fd2aebb7206349ca6190d939a822ced9ed94b1b8
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
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 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
246 Boolean partnerUp;
247 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
248 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
249 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
250 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
251 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
252 int opponentKibitzes;
253 int lastSavedGame; /* [HGM] save: ID of game */
254 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
255 extern int chatCount;
256 int chattingPartner;
257 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
258
259 /* States for ics_getting_history */
260 #define H_FALSE 0
261 #define H_REQUESTED 1
262 #define H_GOT_REQ_HEADER 2
263 #define H_GOT_UNREQ_HEADER 3
264 #define H_GETTING_MOVES 4
265 #define H_GOT_UNWANTED_HEADER 5
266
267 /* whosays values for GameEnds */
268 #define GE_ICS 0
269 #define GE_ENGINE 1
270 #define GE_PLAYER 2
271 #define GE_FILE 3
272 #define GE_XBOARD 4
273 #define GE_ENGINE1 5
274 #define GE_ENGINE2 6
275
276 /* Maximum number of games in a cmail message */
277 #define CMAIL_MAX_GAMES 20
278
279 /* Different types of move when calling RegisterMove */
280 #define CMAIL_MOVE   0
281 #define CMAIL_RESIGN 1
282 #define CMAIL_DRAW   2
283 #define CMAIL_ACCEPT 3
284
285 /* Different types of result to remember for each game */
286 #define CMAIL_NOT_RESULT 0
287 #define CMAIL_OLD_RESULT 1
288 #define CMAIL_NEW_RESULT 2
289
290 /* Telnet protocol constants */
291 #define TN_WILL 0373
292 #define TN_WONT 0374
293 #define TN_DO   0375
294 #define TN_DONT 0376
295 #define TN_IAC  0377
296 #define TN_ECHO 0001
297 #define TN_SGA  0003
298 #define TN_PORT 23
299
300 /* [AS] */
301 static char * safeStrCpy( char * dst, const char * src, size_t count )
302 {
303     assert( dst != NULL );
304     assert( src != NULL );
305     assert( count > 0 );
306
307     strncpy( dst, src, count );
308     dst[ count-1 ] = '\0';
309     return dst;
310 }
311
312 /* Some compiler can't cast u64 to double
313  * This function do the job for us:
314
315  * We use the highest bit for cast, this only
316  * works if the highest bit is not
317  * in use (This should not happen)
318  *
319  * We used this for all compiler
320  */
321 double
322 u64ToDouble(u64 value)
323 {
324   double r;
325   u64 tmp = value & u64Const(0x7fffffffffffffff);
326   r = (double)(s64)tmp;
327   if (value & u64Const(0x8000000000000000))
328        r +=  9.2233720368547758080e18; /* 2^63 */
329  return r;
330 }
331
332 /* Fake up flags for now, as we aren't keeping track of castling
333    availability yet. [HGM] Change of logic: the flag now only
334    indicates the type of castlings allowed by the rule of the game.
335    The actual rights themselves are maintained in the array
336    castlingRights, as part of the game history, and are not probed
337    by this function.
338  */
339 int
340 PosFlags(index)
341 {
342   int flags = F_ALL_CASTLE_OK;
343   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
344   switch (gameInfo.variant) {
345   case VariantSuicide:
346     flags &= ~F_ALL_CASTLE_OK;
347   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
348     flags |= F_IGNORE_CHECK;
349   case VariantLosers:
350     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351     break;
352   case VariantAtomic:
353     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
354     break;
355   case VariantKriegspiel:
356     flags |= F_KRIEGSPIEL_CAPTURE;
357     break;
358   case VariantCapaRandom: 
359   case VariantFischeRandom:
360     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
361   case VariantNoCastle:
362   case VariantShatranj:
363   case VariantCourier:
364   case VariantMakruk:
365     flags &= ~F_ALL_CASTLE_OK;
366     break;
367   default:
368     break;
369   }
370   return flags;
371 }
372
373 FILE *gameFileFP, *debugFP;
374
375 /* 
376     [AS] Note: sometimes, the sscanf() function is used to parse the input
377     into a fixed-size buffer. Because of this, we must be prepared to
378     receive strings as long as the size of the input buffer, which is currently
379     set to 4K for Windows and 8K for the rest.
380     So, we must either allocate sufficiently large buffers here, or
381     reduce the size of the input buffer in the input reading part.
382 */
383
384 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
385 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
386 char thinkOutput1[MSG_SIZ*10];
387
388 ChessProgramState first, second;
389
390 /* premove variables */
391 int premoveToX = 0;
392 int premoveToY = 0;
393 int premoveFromX = 0;
394 int premoveFromY = 0;
395 int premovePromoChar = 0;
396 int gotPremove = 0;
397 Boolean alarmSounded;
398 /* end premove variables */
399
400 char *ics_prefix = "$";
401 int ics_type = ICS_GENERIC;
402
403 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
404 int pauseExamForwardMostMove = 0;
405 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
406 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
407 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
408 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
409 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
410 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
411 int whiteFlag = FALSE, blackFlag = FALSE;
412 int userOfferedDraw = FALSE;
413 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
414 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
415 int cmailMoveType[CMAIL_MAX_GAMES];
416 long ics_clock_paused = 0;
417 ProcRef icsPR = NoProc, cmailPR = NoProc;
418 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
419 GameMode gameMode = BeginningOfGame;
420 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
421 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
422 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
423 int hiddenThinkOutputState = 0; /* [AS] */
424 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
425 int adjudicateLossPlies = 6;
426 char white_holding[64], black_holding[64];
427 TimeMark lastNodeCountTime;
428 long lastNodeCount=0;
429 int have_sent_ICS_logon = 0;
430 int movesPerSession;
431 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
432 long timeControl_2; /* [AS] Allow separate time controls */
433 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
434 long timeRemaining[2][MAX_MOVES];
435 int matchGame = 0;
436 TimeMark programStartTime;
437 char ics_handle[MSG_SIZ];
438 int have_set_title = 0;
439
440 /* animateTraining preserves the state of appData.animate
441  * when Training mode is activated. This allows the
442  * response to be animated when appData.animate == TRUE and
443  * appData.animateDragging == TRUE.
444  */
445 Boolean animateTraining;
446
447 GameInfo gameInfo;
448
449 AppData appData;
450
451 Board boards[MAX_MOVES];
452 /* [HGM] Following 7 needed for accurate legality tests: */
453 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
454 signed char  initialRights[BOARD_FILES];
455 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
456 int   initialRulePlies, FENrulePlies;
457 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int loadFlag = 0; 
459 int shuffleOpenings;
460 int mute; // mute all sounds
461
462 // [HGM] vari: next 12 to save and restore variations
463 #define MAX_VARIATIONS 10
464 int framePtr = MAX_MOVES-1; // points to free stack entry
465 int storedGames = 0;
466 int savedFirst[MAX_VARIATIONS];
467 int savedLast[MAX_VARIATIONS];
468 int savedFramePtr[MAX_VARIATIONS];
469 char *savedDetails[MAX_VARIATIONS];
470 ChessMove savedResult[MAX_VARIATIONS];
471
472 void PushTail P((int firstMove, int lastMove));
473 Boolean PopTail P((Boolean annotate));
474 void CleanupTail P((void));
475
476 ChessSquare  FIDEArray[2][BOARD_FILES] = {
477     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
478         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
479     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
480         BlackKing, BlackBishop, BlackKnight, BlackRook }
481 };
482
483 ChessSquare twoKingsArray[2][BOARD_FILES] = {
484     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
485         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
486     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
487         BlackKing, BlackKing, BlackKnight, BlackRook }
488 };
489
490 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
491     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
492         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
493     { BlackRook, BlackMan, BlackBishop, BlackQueen,
494         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 };
496
497 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
498     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
499         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
500     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
501         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 };
503
504 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
505     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
506         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
507     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
508         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 };
510
511 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
512     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
513         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackMan, BlackFerz,
515         BlackKing, BlackMan, BlackKnight, BlackRook }
516 };
517
518
519 #if (BOARD_FILES>=10)
520 ChessSquare ShogiArray[2][BOARD_FILES] = {
521     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
522         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
523     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
524         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 };
526
527 ChessSquare XiangqiArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
529         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
530     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
531         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 };
533
534 ChessSquare CapablancaArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
536         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
537     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
538         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 };
540
541 ChessSquare GreatArray[2][BOARD_FILES] = {
542     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
543         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
544     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
545         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 };
547
548 ChessSquare JanusArray[2][BOARD_FILES] = {
549     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
550         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
551     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
552         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
553 };
554
555 #ifdef GOTHIC
556 ChessSquare GothicArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
558         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
560         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 };
562 #else // !GOTHIC
563 #define GothicArray CapablancaArray
564 #endif // !GOTHIC
565
566 #ifdef FALCON
567 ChessSquare FalconArray[2][BOARD_FILES] = {
568     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
569         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
570     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
571         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 };
573 #else // !FALCON
574 #define FalconArray CapablancaArray
575 #endif // !FALCON
576
577 #else // !(BOARD_FILES>=10)
578 #define XiangqiPosition FIDEArray
579 #define CapablancaArray FIDEArray
580 #define GothicArray FIDEArray
581 #define GreatArray FIDEArray
582 #endif // !(BOARD_FILES>=10)
583
584 #if (BOARD_FILES>=12)
585 ChessSquare CourierArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
589         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
590 };
591 #else // !(BOARD_FILES>=12)
592 #define CourierArray CapablancaArray
593 #endif // !(BOARD_FILES>=12)
594
595
596 Board initialPosition;
597
598
599 /* Convert str to a rating. Checks for special cases of "----",
600
601    "++++", etc. Also strips ()'s */
602 int
603 string_to_rating(str)
604   char *str;
605 {
606   while(*str && !isdigit(*str)) ++str;
607   if (!*str)
608     return 0;   /* One of the special "no rating" cases */
609   else
610     return atoi(str);
611 }
612
613 void
614 ClearProgramStats()
615 {
616     /* Init programStats */
617     programStats.movelist[0] = 0;
618     programStats.depth = 0;
619     programStats.nr_moves = 0;
620     programStats.moves_left = 0;
621     programStats.nodes = 0;
622     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
623     programStats.score = 0;
624     programStats.got_only_move = 0;
625     programStats.got_fail = 0;
626     programStats.line_is_book = 0;
627 }
628
629 void
630 InitBackEnd1()
631 {
632     int matched, min, sec;
633
634     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
635
636     GetTimeMark(&programStartTime);
637     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638
639     ClearProgramStats();
640     programStats.ok_to_send = 1;
641     programStats.seen_stat = 0;
642
643     /*
644      * Initialize game list
645      */
646     ListNew(&gameList);
647
648
649     /*
650      * Internet chess server status
651      */
652     if (appData.icsActive) {
653         appData.matchMode = FALSE;
654         appData.matchGames = 0;
655 #if ZIPPY       
656         appData.noChessProgram = !appData.zippyPlay;
657 #else
658         appData.zippyPlay = FALSE;
659         appData.zippyTalk = FALSE;
660         appData.noChessProgram = TRUE;
661 #endif
662         if (*appData.icsHelper != NULLCHAR) {
663             appData.useTelnet = TRUE;
664             appData.telnetProgram = appData.icsHelper;
665         }
666     } else {
667         appData.zippyTalk = appData.zippyPlay = FALSE;
668     }
669
670     /* [AS] Initialize pv info list [HGM] and game state */
671     {
672         int i, j;
673
674         for( i=0; i<=framePtr; i++ ) {
675             pvInfoList[i].depth = -1;
676             boards[i][EP_STATUS] = EP_NONE;
677             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
678         }
679     }
680
681     /*
682      * Parse timeControl resource
683      */
684     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
685                           appData.movesPerSession)) {
686         char buf[MSG_SIZ];
687         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
688         DisplayFatalError(buf, 0, 2);
689     }
690
691     /*
692      * Parse searchTime resource
693      */
694     if (*appData.searchTime != NULLCHAR) {
695         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
696         if (matched == 1) {
697             searchTime = min * 60;
698         } else if (matched == 2) {
699             searchTime = min * 60 + sec;
700         } else {
701             char buf[MSG_SIZ];
702             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
703             DisplayFatalError(buf, 0, 2);
704         }
705     }
706
707     /* [AS] Adjudication threshold */
708     adjudicateLossThreshold = appData.adjudicateLossThreshold;
709     
710     first.which = "first";
711     second.which = "second";
712     first.maybeThinking = second.maybeThinking = FALSE;
713     first.pr = second.pr = NoProc;
714     first.isr = second.isr = NULL;
715     first.sendTime = second.sendTime = 2;
716     first.sendDrawOffers = 1;
717     if (appData.firstPlaysBlack) {
718         first.twoMachinesColor = "black\n";
719         second.twoMachinesColor = "white\n";
720     } else {
721         first.twoMachinesColor = "white\n";
722         second.twoMachinesColor = "black\n";
723     }
724     first.program = appData.firstChessProgram;
725     second.program = appData.secondChessProgram;
726     first.host = appData.firstHost;
727     second.host = appData.secondHost;
728     first.dir = appData.firstDirectory;
729     second.dir = appData.secondDirectory;
730     first.other = &second;
731     second.other = &first;
732     first.initString = appData.initString;
733     second.initString = appData.secondInitString;
734     first.computerString = appData.firstComputerString;
735     second.computerString = appData.secondComputerString;
736     first.useSigint = second.useSigint = TRUE;
737     first.useSigterm = second.useSigterm = TRUE;
738     first.reuse = appData.reuseFirst;
739     second.reuse = appData.reuseSecond;
740     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
741     second.nps = appData.secondNPS;
742     first.useSetboard = second.useSetboard = FALSE;
743     first.useSAN = second.useSAN = FALSE;
744     first.usePing = second.usePing = FALSE;
745     first.lastPing = second.lastPing = 0;
746     first.lastPong = second.lastPong = 0;
747     first.usePlayother = second.usePlayother = FALSE;
748     first.useColors = second.useColors = TRUE;
749     first.useUsermove = second.useUsermove = FALSE;
750     first.sendICS = second.sendICS = FALSE;
751     first.sendName = second.sendName = appData.icsActive;
752     first.sdKludge = second.sdKludge = FALSE;
753     first.stKludge = second.stKludge = FALSE;
754     TidyProgramName(first.program, first.host, first.tidy);
755     TidyProgramName(second.program, second.host, second.tidy);
756     first.matchWins = second.matchWins = 0;
757     strcpy(first.variants, appData.variant);
758     strcpy(second.variants, appData.variant);
759     first.analysisSupport = second.analysisSupport = 2; /* detect */
760     first.analyzing = second.analyzing = FALSE;
761     first.initDone = second.initDone = FALSE;
762
763     /* New features added by Tord: */
764     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
765     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
766     /* End of new features added by Tord. */
767     first.fenOverride  = appData.fenOverride1;
768     second.fenOverride = appData.fenOverride2;
769
770     /* [HGM] time odds: set factor for each machine */
771     first.timeOdds  = appData.firstTimeOdds;
772     second.timeOdds = appData.secondTimeOdds;
773     { float norm = 1;
774         if(appData.timeOddsMode) {
775             norm = first.timeOdds;
776             if(norm > second.timeOdds) norm = second.timeOdds;
777         }
778         first.timeOdds /= norm;
779         second.timeOdds /= norm;
780     }
781
782     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
783     first.accumulateTC = appData.firstAccumulateTC;
784     second.accumulateTC = appData.secondAccumulateTC;
785     first.maxNrOfSessions = second.maxNrOfSessions = 1;
786
787     /* [HGM] debug */
788     first.debug = second.debug = FALSE;
789     first.supportsNPS = second.supportsNPS = UNKNOWN;
790
791     /* [HGM] options */
792     first.optionSettings  = appData.firstOptions;
793     second.optionSettings = appData.secondOptions;
794
795     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
796     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
797     first.isUCI = appData.firstIsUCI; /* [AS] */
798     second.isUCI = appData.secondIsUCI; /* [AS] */
799     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
800     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
801
802     if (appData.firstProtocolVersion > PROTOVER ||
803         appData.firstProtocolVersion < 1) {
804       char buf[MSG_SIZ];
805       sprintf(buf, _("protocol version %d not supported"),
806               appData.firstProtocolVersion);
807       DisplayFatalError(buf, 0, 2);
808     } else {
809       first.protocolVersion = appData.firstProtocolVersion;
810     }
811
812     if (appData.secondProtocolVersion > PROTOVER ||
813         appData.secondProtocolVersion < 1) {
814       char buf[MSG_SIZ];
815       sprintf(buf, _("protocol version %d not supported"),
816               appData.secondProtocolVersion);
817       DisplayFatalError(buf, 0, 2);
818     } else {
819       second.protocolVersion = appData.secondProtocolVersion;
820     }
821
822     if (appData.icsActive) {
823         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
824 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
825     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
826         appData.clockMode = FALSE;
827         first.sendTime = second.sendTime = 0;
828     }
829     
830 #if ZIPPY
831     /* Override some settings from environment variables, for backward
832        compatibility.  Unfortunately it's not feasible to have the env
833        vars just set defaults, at least in xboard.  Ugh.
834     */
835     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
836       ZippyInit();
837     }
838 #endif
839     
840     if (appData.noChessProgram) {
841         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
842         sprintf(programVersion, "%s", PACKAGE_STRING);
843     } else {
844       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847     }
848
849     if (!appData.icsActive) {
850       char buf[MSG_SIZ];
851       /* Check for variants that are supported only in ICS mode,
852          or not at all.  Some that are accepted here nevertheless
853          have bugs; see comments below.
854       */
855       VariantClass variant = StringToVariant(appData.variant);
856       switch (variant) {
857       case VariantBughouse:     /* need four players and two boards */
858       case VariantKriegspiel:   /* need to hide pieces and move details */
859       /* case VariantFischeRandom: (Fabien: moved below) */
860         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
861         DisplayFatalError(buf, 0, 2);
862         return;
863
864       case VariantUnknown:
865       case VariantLoadable:
866       case Variant29:
867       case Variant30:
868       case Variant31:
869       case Variant32:
870       case Variant33:
871       case Variant34:
872       case Variant35:
873       case Variant36:
874       default:
875         sprintf(buf, _("Unknown variant name %s"), appData.variant);
876         DisplayFatalError(buf, 0, 2);
877         return;
878
879       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
880       case VariantFairy:      /* [HGM] TestLegality definitely off! */
881       case VariantGothic:     /* [HGM] should work */
882       case VariantCapablanca: /* [HGM] should work */
883       case VariantCourier:    /* [HGM] initial forced moves not implemented */
884       case VariantShogi:      /* [HGM] drops not tested for legality */
885       case VariantKnightmate: /* [HGM] should work */
886       case VariantCylinder:   /* [HGM] untested */
887       case VariantFalcon:     /* [HGM] untested */
888       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
889                                  offboard interposition not understood */
890       case VariantNormal:     /* definitely works! */
891       case VariantWildCastle: /* pieces not automatically shuffled */
892       case VariantNoCastle:   /* pieces not automatically shuffled */
893       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
894       case VariantLosers:     /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantSuicide:    /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantGiveaway:   /* should work except for win condition,
899                                  and doesn't know captures are mandatory */
900       case VariantTwoKings:   /* should work */
901       case VariantAtomic:     /* should work except for win condition */
902       case Variant3Check:     /* should work except for win condition */
903       case VariantShatranj:   /* should work except for all win conditions */
904       case VariantMakruk:     /* should work except for daw countdown */
905       case VariantBerolina:   /* might work if TestLegality is off */
906       case VariantCapaRandom: /* should work */
907       case VariantJanus:      /* should work */
908       case VariantSuper:      /* experimental */
909       case VariantGreat:      /* experimental, requires legality testing to be off */
910         break;
911       }
912     }
913
914     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
915     InitEngineUCI( installDir, &second );
916 }
917
918 int NextIntegerFromString( char ** str, long * value )
919 {
920     int result = -1;
921     char * s = *str;
922
923     while( *s == ' ' || *s == '\t' ) {
924         s++;
925     }
926
927     *value = 0;
928
929     if( *s >= '0' && *s <= '9' ) {
930         while( *s >= '0' && *s <= '9' ) {
931             *value = *value * 10 + (*s - '0');
932             s++;
933         }
934
935         result = 0;
936     }
937
938     *str = s;
939
940     return result;
941 }
942
943 int NextTimeControlFromString( char ** str, long * value )
944 {
945     long temp;
946     int result = NextIntegerFromString( str, &temp );
947
948     if( result == 0 ) {
949         *value = temp * 60; /* Minutes */
950         if( **str == ':' ) {
951             (*str)++;
952             result = NextIntegerFromString( str, &temp );
953             *value += temp; /* Seconds */
954         }
955     }
956
957     return result;
958 }
959
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
962     int result = -1; long temp, temp2;
963
964     if(**str != '+') return -1; // old params remain in force!
965     (*str)++;
966     if( NextTimeControlFromString( str, &temp ) ) return -1;
967
968     if(**str != '/') {
969         /* time only: incremental or sudden-death time control */
970         if(**str == '+') { /* increment follows; read it */
971             (*str)++;
972             if(result = NextIntegerFromString( str, &temp2)) return -1;
973             *inc = temp2 * 1000;
974         } else *inc = 0;
975         *moves = 0; *tc = temp * 1000; 
976         return 0;
977     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
978
979     (*str)++; /* classical time control */
980     result = NextTimeControlFromString( str, &temp2);
981     if(result == 0) {
982         *moves = temp/60;
983         *tc    = temp2 * 1000;
984         *inc   = 0;
985     }
986     return result;
987 }
988
989 int GetTimeQuota(int movenr)
990 {   /* [HGM] get time to add from the multi-session time-control string */
991     int moves=1; /* kludge to force reading of first session */
992     long time, increment;
993     char *s = fullTimeControlString;
994
995     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
996     do {
997         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999         if(movenr == -1) return time;    /* last move before new session     */
1000         if(!moves) return increment;     /* current session is incremental   */
1001         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002     } while(movenr >= -1);               /* try again for next session       */
1003
1004     return 0; // no new time quota on this move
1005 }
1006
1007 int
1008 ParseTimeControl(tc, ti, mps)
1009      char *tc;
1010      int ti;
1011      int mps;
1012 {
1013   long tc1;
1014   long tc2;
1015   char buf[MSG_SIZ];
1016   
1017   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018   if(ti > 0) {
1019     if(mps)
1020       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1021     else sprintf(buf, "+%s+%d", tc, ti);
1022   } else {
1023     if(mps)
1024              sprintf(buf, "+%d/%s", mps, tc);
1025     else sprintf(buf, "+%s", tc);
1026   }
1027   fullTimeControlString = StrSave(buf);
1028   
1029   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1030     return FALSE;
1031   }
1032   
1033   if( *tc == '/' ) {
1034     /* Parse second time control */
1035     tc++;
1036     
1037     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1038       return FALSE;
1039     }
1040     
1041     if( tc2 == 0 ) {
1042       return FALSE;
1043     }
1044     
1045     timeControl_2 = tc2 * 1000;
1046   }
1047   else {
1048     timeControl_2 = 0;
1049   }
1050   
1051   if( tc1 == 0 ) {
1052     return FALSE;
1053   }
1054   
1055   timeControl = tc1 * 1000;
1056   
1057   if (ti >= 0) {
1058     timeIncrement = ti * 1000;  /* convert to ms */
1059     movesPerSession = 0;
1060   } else {
1061     timeIncrement = 0;
1062     movesPerSession = mps;
1063   }
1064   return TRUE;
1065 }
1066
1067 void
1068 InitBackEnd2()
1069 {
1070     if (appData.debugMode) {
1071         fprintf(debugFP, "%s\n", programVersion);
1072     }
1073
1074     set_cont_sequence(appData.wrapContSeq);
1075     if (appData.matchGames > 0) {
1076         appData.matchMode = TRUE;
1077     } else if (appData.matchMode) {
1078         appData.matchGames = 1;
1079     }
1080     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1081         appData.matchGames = appData.sameColorGames;
1082     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1083         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1084         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085     }
1086     Reset(TRUE, FALSE);
1087     if (appData.noChessProgram || first.protocolVersion == 1) {
1088       InitBackEnd3();
1089     } else {
1090       /* kludge: allow timeout for initial "feature" commands */
1091       FreezeUI();
1092       DisplayMessage("", _("Starting chess program"));
1093       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1094     }
1095 }
1096
1097 void
1098 InitBackEnd3 P((void))
1099 {
1100     GameMode initialMode;
1101     char buf[MSG_SIZ];
1102     int err;
1103
1104     InitChessProgram(&first, startedFromSetupPosition);
1105
1106
1107     if (appData.icsActive) {
1108 #ifdef WIN32
1109         /* [DM] Make a console window if needed [HGM] merged ifs */
1110         ConsoleCreate(); 
1111 #endif
1112         err = establish();
1113         if (err != 0) {
1114             if (*appData.icsCommPort != NULLCHAR) {
1115                 sprintf(buf, _("Could not open comm port %s"),  
1116                         appData.icsCommPort);
1117             } else {
1118                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1119                         appData.icsHost, appData.icsPort);
1120             }
1121             DisplayFatalError(buf, err, 1);
1122             return;
1123         }
1124         SetICSMode();
1125         telnetISR =
1126           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1127         fromUserISR =
1128           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1131     } else if (appData.noChessProgram) {
1132         SetNCPMode();
1133     } else {
1134         SetGNUMode();
1135     }
1136
1137     if (*appData.cmailGameName != NULLCHAR) {
1138         SetCmailMode();
1139         OpenLoopback(&cmailPR);
1140         cmailISR =
1141           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1142     }
1143     
1144     ThawUI();
1145     DisplayMessage("", "");
1146     if (StrCaseCmp(appData.initialMode, "") == 0) {
1147       initialMode = BeginningOfGame;
1148     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1149       initialMode = TwoMachinesPlay;
1150     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1151       initialMode = AnalyzeFile; 
1152     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1153       initialMode = AnalyzeMode;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1155       initialMode = MachinePlaysWhite;
1156     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1157       initialMode = MachinePlaysBlack;
1158     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1159       initialMode = EditGame;
1160     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1161       initialMode = EditPosition;
1162     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1163       initialMode = Training;
1164     } else {
1165       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1166       DisplayFatalError(buf, 0, 2);
1167       return;
1168     }
1169
1170     if (appData.matchMode) {
1171         /* Set up machine vs. machine match */
1172         if (appData.noChessProgram) {
1173             DisplayFatalError(_("Can't have a match with no chess programs"),
1174                               0, 2);
1175             return;
1176         }
1177         matchMode = TRUE;
1178         matchGame = 1;
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             int index = appData.loadGameIndex; // [HGM] autoinc
1181             if(index<0) lastIndex = index = 1;
1182             if (!LoadGameFromFile(appData.loadGameFile,
1183                                   index,
1184                                   appData.loadGameFile, FALSE)) {
1185                 DisplayFatalError(_("Bad game file"), 0, 1);
1186                 return;
1187             }
1188         } else if (*appData.loadPositionFile != NULLCHAR) {
1189             int index = appData.loadPositionIndex; // [HGM] autoinc
1190             if(index<0) lastIndex = index = 1;
1191             if (!LoadPositionFromFile(appData.loadPositionFile,
1192                                       index,
1193                                       appData.loadPositionFile)) {
1194                 DisplayFatalError(_("Bad position file"), 0, 1);
1195                 return;
1196             }
1197         }
1198         TwoMachinesEvent();
1199     } else if (*appData.cmailGameName != NULLCHAR) {
1200         /* Set up cmail mode */
1201         ReloadCmailMsgEvent(TRUE);
1202     } else {
1203         /* Set up other modes */
1204         if (initialMode == AnalyzeFile) {
1205           if (*appData.loadGameFile == NULLCHAR) {
1206             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1207             return;
1208           }
1209         }
1210         if (*appData.loadGameFile != NULLCHAR) {
1211             (void) LoadGameFromFile(appData.loadGameFile,
1212                                     appData.loadGameIndex,
1213                                     appData.loadGameFile, TRUE);
1214         } else if (*appData.loadPositionFile != NULLCHAR) {
1215             (void) LoadPositionFromFile(appData.loadPositionFile,
1216                                         appData.loadPositionIndex,
1217                                         appData.loadPositionFile);
1218             /* [HGM] try to make self-starting even after FEN load */
1219             /* to allow automatic setup of fairy variants with wtm */
1220             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1221                 gameMode = BeginningOfGame;
1222                 setboardSpoiledMachineBlack = 1;
1223             }
1224             /* [HGM] loadPos: make that every new game uses the setup */
1225             /* from file as long as we do not switch variant          */
1226             if(!blackPlaysFirst) {
1227                 startedFromPositionFile = TRUE;
1228                 CopyBoard(filePosition, boards[0]);
1229             }
1230         }
1231         if (initialMode == AnalyzeMode) {
1232           if (appData.noChessProgram) {
1233             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1234             return;
1235           }
1236           if (appData.icsActive) {
1237             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1238             return;
1239           }
1240           AnalyzeModeEvent();
1241         } else if (initialMode == AnalyzeFile) {
1242           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1243           ShowThinkingEvent();
1244           AnalyzeFileEvent();
1245           AnalysisPeriodicEvent(1);
1246         } else if (initialMode == MachinePlaysWhite) {
1247           if (appData.noChessProgram) {
1248             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1249                               0, 2);
1250             return;
1251           }
1252           if (appData.icsActive) {
1253             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1254                               0, 2);
1255             return;
1256           }
1257           MachineWhiteEvent();
1258         } else if (initialMode == MachinePlaysBlack) {
1259           if (appData.noChessProgram) {
1260             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1261                               0, 2);
1262             return;
1263           }
1264           if (appData.icsActive) {
1265             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1266                               0, 2);
1267             return;
1268           }
1269           MachineBlackEvent();
1270         } else if (initialMode == TwoMachinesPlay) {
1271           if (appData.noChessProgram) {
1272             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1273                               0, 2);
1274             return;
1275           }
1276           if (appData.icsActive) {
1277             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1278                               0, 2);
1279             return;
1280           }
1281           TwoMachinesEvent();
1282         } else if (initialMode == EditGame) {
1283           EditGameEvent();
1284         } else if (initialMode == EditPosition) {
1285           EditPositionEvent();
1286         } else if (initialMode == Training) {
1287           if (*appData.loadGameFile == NULLCHAR) {
1288             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1289             return;
1290           }
1291           TrainingEvent();
1292         }
1293     }
1294 }
1295
1296 /*
1297  * Establish will establish a contact to a remote host.port.
1298  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1299  *  used to talk to the host.
1300  * Returns 0 if okay, error code if not.
1301  */
1302 int
1303 establish()
1304 {
1305     char buf[MSG_SIZ];
1306
1307     if (*appData.icsCommPort != NULLCHAR) {
1308         /* Talk to the host through a serial comm port */
1309         return OpenCommPort(appData.icsCommPort, &icsPR);
1310
1311     } else if (*appData.gateway != NULLCHAR) {
1312         if (*appData.remoteShell == NULLCHAR) {
1313             /* Use the rcmd protocol to run telnet program on a gateway host */
1314             snprintf(buf, sizeof(buf), "%s %s %s",
1315                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1316             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1317
1318         } else {
1319             /* Use the rsh program to run telnet program on a gateway host */
1320             if (*appData.remoteUser == NULLCHAR) {
1321                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1322                         appData.gateway, appData.telnetProgram,
1323                         appData.icsHost, appData.icsPort);
1324             } else {
1325                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1326                         appData.remoteShell, appData.gateway, 
1327                         appData.remoteUser, appData.telnetProgram,
1328                         appData.icsHost, appData.icsPort);
1329             }
1330             return StartChildProcess(buf, "", &icsPR);
1331
1332         }
1333     } else if (appData.useTelnet) {
1334         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1335
1336     } else {
1337         /* TCP socket interface differs somewhat between
1338            Unix and NT; handle details in the front end.
1339            */
1340         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1341     }
1342 }
1343
1344 void
1345 show_bytes(fp, buf, count)
1346      FILE *fp;
1347      char *buf;
1348      int count;
1349 {
1350     while (count--) {
1351         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1352             fprintf(fp, "\\%03o", *buf & 0xff);
1353         } else {
1354             putc(*buf, fp);
1355         }
1356         buf++;
1357     }
1358     fflush(fp);
1359 }
1360
1361 /* Returns an errno value */
1362 int
1363 OutputMaybeTelnet(pr, message, count, outError)
1364      ProcRef pr;
1365      char *message;
1366      int count;
1367      int *outError;
1368 {
1369     char buf[8192], *p, *q, *buflim;
1370     int left, newcount, outcount;
1371
1372     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1373         *appData.gateway != NULLCHAR) {
1374         if (appData.debugMode) {
1375             fprintf(debugFP, ">ICS: ");
1376             show_bytes(debugFP, message, count);
1377             fprintf(debugFP, "\n");
1378         }
1379         return OutputToProcess(pr, message, count, outError);
1380     }
1381
1382     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1383     p = message;
1384     q = buf;
1385     left = count;
1386     newcount = 0;
1387     while (left) {
1388         if (q >= buflim) {
1389             if (appData.debugMode) {
1390                 fprintf(debugFP, ">ICS: ");
1391                 show_bytes(debugFP, buf, newcount);
1392                 fprintf(debugFP, "\n");
1393             }
1394             outcount = OutputToProcess(pr, buf, newcount, outError);
1395             if (outcount < newcount) return -1; /* to be sure */
1396             q = buf;
1397             newcount = 0;
1398         }
1399         if (*p == '\n') {
1400             *q++ = '\r';
1401             newcount++;
1402         } else if (((unsigned char) *p) == TN_IAC) {
1403             *q++ = (char) TN_IAC;
1404             newcount ++;
1405         }
1406         *q++ = *p++;
1407         newcount++;
1408         left--;
1409     }
1410     if (appData.debugMode) {
1411         fprintf(debugFP, ">ICS: ");
1412         show_bytes(debugFP, buf, newcount);
1413         fprintf(debugFP, "\n");
1414     }
1415     outcount = OutputToProcess(pr, buf, newcount, outError);
1416     if (outcount < newcount) return -1; /* to be sure */
1417     return count;
1418 }
1419
1420 void
1421 read_from_player(isr, closure, message, count, error)
1422      InputSourceRef isr;
1423      VOIDSTAR closure;
1424      char *message;
1425      int count;
1426      int error;
1427 {
1428     int outError, outCount;
1429     static int gotEof = 0;
1430
1431     /* Pass data read from player on to ICS */
1432     if (count > 0) {
1433         gotEof = 0;
1434         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1435         if (outCount < count) {
1436             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1437         }
1438     } else if (count < 0) {
1439         RemoveInputSource(isr);
1440         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1441     } else if (gotEof++ > 0) {
1442         RemoveInputSource(isr);
1443         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1444     }
1445 }
1446
1447 void
1448 KeepAlive()
1449 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1450     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1451     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1452     SendToICS("date\n");
1453     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1454 }
1455
1456 /* added routine for printf style output to ics */
1457 void ics_printf(char *format, ...)
1458 {
1459     char buffer[MSG_SIZ];
1460     va_list args;
1461
1462     va_start(args, format);
1463     vsnprintf(buffer, sizeof(buffer), format, args);
1464     buffer[sizeof(buffer)-1] = '\0';
1465     SendToICS(buffer);
1466     va_end(args);
1467 }
1468
1469 void
1470 SendToICS(s)
1471      char *s;
1472 {
1473     int count, outCount, outError;
1474
1475     if (icsPR == NULL) return;
1476
1477     count = strlen(s);
1478     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1479     if (outCount < count) {
1480         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1481     }
1482 }
1483
1484 /* This is used for sending logon scripts to the ICS. Sending
1485    without a delay causes problems when using timestamp on ICC
1486    (at least on my machine). */
1487 void
1488 SendToICSDelayed(s,msdelay)
1489      char *s;
1490      long msdelay;
1491 {
1492     int count, outCount, outError;
1493
1494     if (icsPR == NULL) return;
1495
1496     count = strlen(s);
1497     if (appData.debugMode) {
1498         fprintf(debugFP, ">ICS: ");
1499         show_bytes(debugFP, s, count);
1500         fprintf(debugFP, "\n");
1501     }
1502     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1503                                       msdelay);
1504     if (outCount < count) {
1505         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506     }
1507 }
1508
1509
1510 /* Remove all highlighting escape sequences in s
1511    Also deletes any suffix starting with '(' 
1512    */
1513 char *
1514 StripHighlightAndTitle(s)
1515      char *s;
1516 {
1517     static char retbuf[MSG_SIZ];
1518     char *p = retbuf;
1519
1520     while (*s != NULLCHAR) {
1521         while (*s == '\033') {
1522             while (*s != NULLCHAR && !isalpha(*s)) s++;
1523             if (*s != NULLCHAR) s++;
1524         }
1525         while (*s != NULLCHAR && *s != '\033') {
1526             if (*s == '(' || *s == '[') {
1527                 *p = NULLCHAR;
1528                 return retbuf;
1529             }
1530             *p++ = *s++;
1531         }
1532     }
1533     *p = NULLCHAR;
1534     return retbuf;
1535 }
1536
1537 /* Remove all highlighting escape sequences in s */
1538 char *
1539 StripHighlight(s)
1540      char *s;
1541 {
1542     static char retbuf[MSG_SIZ];
1543     char *p = retbuf;
1544
1545     while (*s != NULLCHAR) {
1546         while (*s == '\033') {
1547             while (*s != NULLCHAR && !isalpha(*s)) s++;
1548             if (*s != NULLCHAR) s++;
1549         }
1550         while (*s != NULLCHAR && *s != '\033') {
1551             *p++ = *s++;
1552         }
1553     }
1554     *p = NULLCHAR;
1555     return retbuf;
1556 }
1557
1558 char *variantNames[] = VARIANT_NAMES;
1559 char *
1560 VariantName(v)
1561      VariantClass v;
1562 {
1563     return variantNames[v];
1564 }
1565
1566
1567 /* Identify a variant from the strings the chess servers use or the
1568    PGN Variant tag names we use. */
1569 VariantClass
1570 StringToVariant(e)
1571      char *e;
1572 {
1573     char *p;
1574     int wnum = -1;
1575     VariantClass v = VariantNormal;
1576     int i, found = FALSE;
1577     char buf[MSG_SIZ];
1578
1579     if (!e) return v;
1580
1581     /* [HGM] skip over optional board-size prefixes */
1582     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1583         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1584         while( *e++ != '_');
1585     }
1586
1587     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1588         v = VariantNormal;
1589         found = TRUE;
1590     } else
1591     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1592       if (StrCaseStr(e, variantNames[i])) {
1593         v = (VariantClass) i;
1594         found = TRUE;
1595         break;
1596       }
1597     }
1598
1599     if (!found) {
1600       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1601           || StrCaseStr(e, "wild/fr") 
1602           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1603         v = VariantFischeRandom;
1604       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1605                  (i = 1, p = StrCaseStr(e, "w"))) {
1606         p += i;
1607         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1608         if (isdigit(*p)) {
1609           wnum = atoi(p);
1610         } else {
1611           wnum = -1;
1612         }
1613         switch (wnum) {
1614         case 0: /* FICS only, actually */
1615         case 1:
1616           /* Castling legal even if K starts on d-file */
1617           v = VariantWildCastle;
1618           break;
1619         case 2:
1620         case 3:
1621         case 4:
1622           /* Castling illegal even if K & R happen to start in
1623              normal positions. */
1624           v = VariantNoCastle;
1625           break;
1626         case 5:
1627         case 7:
1628         case 8:
1629         case 10:
1630         case 11:
1631         case 12:
1632         case 13:
1633         case 14:
1634         case 15:
1635         case 18:
1636         case 19:
1637           /* Castling legal iff K & R start in normal positions */
1638           v = VariantNormal;
1639           break;
1640         case 6:
1641         case 20:
1642         case 21:
1643           /* Special wilds for position setup; unclear what to do here */
1644           v = VariantLoadable;
1645           break;
1646         case 9:
1647           /* Bizarre ICC game */
1648           v = VariantTwoKings;
1649           break;
1650         case 16:
1651           v = VariantKriegspiel;
1652           break;
1653         case 17:
1654           v = VariantLosers;
1655           break;
1656         case 22:
1657           v = VariantFischeRandom;
1658           break;
1659         case 23:
1660           v = VariantCrazyhouse;
1661           break;
1662         case 24:
1663           v = VariantBughouse;
1664           break;
1665         case 25:
1666           v = Variant3Check;
1667           break;
1668         case 26:
1669           /* Not quite the same as FICS suicide! */
1670           v = VariantGiveaway;
1671           break;
1672         case 27:
1673           v = VariantAtomic;
1674           break;
1675         case 28:
1676           v = VariantShatranj;
1677           break;
1678
1679         /* Temporary names for future ICC types.  The name *will* change in 
1680            the next xboard/WinBoard release after ICC defines it. */
1681         case 29:
1682           v = Variant29;
1683           break;
1684         case 30:
1685           v = Variant30;
1686           break;
1687         case 31:
1688           v = Variant31;
1689           break;
1690         case 32:
1691           v = Variant32;
1692           break;
1693         case 33:
1694           v = Variant33;
1695           break;
1696         case 34:
1697           v = Variant34;
1698           break;
1699         case 35:
1700           v = Variant35;
1701           break;
1702         case 36:
1703           v = Variant36;
1704           break;
1705         case 37:
1706           v = VariantShogi;
1707           break;
1708         case 38:
1709           v = VariantXiangqi;
1710           break;
1711         case 39:
1712           v = VariantCourier;
1713           break;
1714         case 40:
1715           v = VariantGothic;
1716           break;
1717         case 41:
1718           v = VariantCapablanca;
1719           break;
1720         case 42:
1721           v = VariantKnightmate;
1722           break;
1723         case 43:
1724           v = VariantFairy;
1725           break;
1726         case 44:
1727           v = VariantCylinder;
1728           break;
1729         case 45:
1730           v = VariantFalcon;
1731           break;
1732         case 46:
1733           v = VariantCapaRandom;
1734           break;
1735         case 47:
1736           v = VariantBerolina;
1737           break;
1738         case 48:
1739           v = VariantJanus;
1740           break;
1741         case 49:
1742           v = VariantSuper;
1743           break;
1744         case 50:
1745           v = VariantGreat;
1746           break;
1747         case -1:
1748           /* Found "wild" or "w" in the string but no number;
1749              must assume it's normal chess. */
1750           v = VariantNormal;
1751           break;
1752         default:
1753           sprintf(buf, _("Unknown wild type %d"), wnum);
1754           DisplayError(buf, 0);
1755           v = VariantUnknown;
1756           break;
1757         }
1758       }
1759     }
1760     if (appData.debugMode) {
1761       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1762               e, wnum, VariantName(v));
1763     }
1764     return v;
1765 }
1766
1767 static int leftover_start = 0, leftover_len = 0;
1768 char star_match[STAR_MATCH_N][MSG_SIZ];
1769
1770 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1771    advance *index beyond it, and set leftover_start to the new value of
1772    *index; else return FALSE.  If pattern contains the character '*', it
1773    matches any sequence of characters not containing '\r', '\n', or the
1774    character following the '*' (if any), and the matched sequence(s) are
1775    copied into star_match.
1776    */
1777 int
1778 looking_at(buf, index, pattern)
1779      char *buf;
1780      int *index;
1781      char *pattern;
1782 {
1783     char *bufp = &buf[*index], *patternp = pattern;
1784     int star_count = 0;
1785     char *matchp = star_match[0];
1786     
1787     for (;;) {
1788         if (*patternp == NULLCHAR) {
1789             *index = leftover_start = bufp - buf;
1790             *matchp = NULLCHAR;
1791             return TRUE;
1792         }
1793         if (*bufp == NULLCHAR) return FALSE;
1794         if (*patternp == '*') {
1795             if (*bufp == *(patternp + 1)) {
1796                 *matchp = NULLCHAR;
1797                 matchp = star_match[++star_count];
1798                 patternp += 2;
1799                 bufp++;
1800                 continue;
1801             } else if (*bufp == '\n' || *bufp == '\r') {
1802                 patternp++;
1803                 if (*patternp == NULLCHAR)
1804                   continue;
1805                 else
1806                   return FALSE;
1807             } else {
1808                 *matchp++ = *bufp++;
1809                 continue;
1810             }
1811         }
1812         if (*patternp != *bufp) return FALSE;
1813         patternp++;
1814         bufp++;
1815     }
1816 }
1817
1818 void
1819 SendToPlayer(data, length)
1820      char *data;
1821      int length;
1822 {
1823     int error, outCount;
1824     outCount = OutputToProcess(NoProc, data, length, &error);
1825     if (outCount < length) {
1826         DisplayFatalError(_("Error writing to display"), error, 1);
1827     }
1828 }
1829
1830 void
1831 PackHolding(packed, holding)
1832      char packed[];
1833      char *holding;
1834 {
1835     char *p = holding;
1836     char *q = packed;
1837     int runlength = 0;
1838     int curr = 9999;
1839     do {
1840         if (*p == curr) {
1841             runlength++;
1842         } else {
1843             switch (runlength) {
1844               case 0:
1845                 break;
1846               case 1:
1847                 *q++ = curr;
1848                 break;
1849               case 2:
1850                 *q++ = curr;
1851                 *q++ = curr;
1852                 break;
1853               default:
1854                 sprintf(q, "%d", runlength);
1855                 while (*q) q++;
1856                 *q++ = curr;
1857                 break;
1858             }
1859             runlength = 1;
1860             curr = *p;
1861         }
1862     } while (*p++);
1863     *q = NULLCHAR;
1864 }
1865
1866 /* Telnet protocol requests from the front end */
1867 void
1868 TelnetRequest(ddww, option)
1869      unsigned char ddww, option;
1870 {
1871     unsigned char msg[3];
1872     int outCount, outError;
1873
1874     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1875
1876     if (appData.debugMode) {
1877         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1878         switch (ddww) {
1879           case TN_DO:
1880             ddwwStr = "DO";
1881             break;
1882           case TN_DONT:
1883             ddwwStr = "DONT";
1884             break;
1885           case TN_WILL:
1886             ddwwStr = "WILL";
1887             break;
1888           case TN_WONT:
1889             ddwwStr = "WONT";
1890             break;
1891           default:
1892             ddwwStr = buf1;
1893             sprintf(buf1, "%d", ddww);
1894             break;
1895         }
1896         switch (option) {
1897           case TN_ECHO:
1898             optionStr = "ECHO";
1899             break;
1900           default:
1901             optionStr = buf2;
1902             sprintf(buf2, "%d", option);
1903             break;
1904         }
1905         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1906     }
1907     msg[0] = TN_IAC;
1908     msg[1] = ddww;
1909     msg[2] = option;
1910     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1911     if (outCount < 3) {
1912         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1913     }
1914 }
1915
1916 void
1917 DoEcho()
1918 {
1919     if (!appData.icsActive) return;
1920     TelnetRequest(TN_DO, TN_ECHO);
1921 }
1922
1923 void
1924 DontEcho()
1925 {
1926     if (!appData.icsActive) return;
1927     TelnetRequest(TN_DONT, TN_ECHO);
1928 }
1929
1930 void
1931 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1932 {
1933     /* put the holdings sent to us by the server on the board holdings area */
1934     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1935     char p;
1936     ChessSquare piece;
1937
1938     if(gameInfo.holdingsWidth < 2)  return;
1939     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1940         return; // prevent overwriting by pre-board holdings
1941
1942     if( (int)lowestPiece >= BlackPawn ) {
1943         holdingsColumn = 0;
1944         countsColumn = 1;
1945         holdingsStartRow = BOARD_HEIGHT-1;
1946         direction = -1;
1947     } else {
1948         holdingsColumn = BOARD_WIDTH-1;
1949         countsColumn = BOARD_WIDTH-2;
1950         holdingsStartRow = 0;
1951         direction = 1;
1952     }
1953
1954     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1955         board[i][holdingsColumn] = EmptySquare;
1956         board[i][countsColumn]   = (ChessSquare) 0;
1957     }
1958     while( (p=*holdings++) != NULLCHAR ) {
1959         piece = CharToPiece( ToUpper(p) );
1960         if(piece == EmptySquare) continue;
1961         /*j = (int) piece - (int) WhitePawn;*/
1962         j = PieceToNumber(piece);
1963         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1964         if(j < 0) continue;               /* should not happen */
1965         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1966         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1967         board[holdingsStartRow+j*direction][countsColumn]++;
1968     }
1969 }
1970
1971
1972 void
1973 VariantSwitch(Board board, VariantClass newVariant)
1974 {
1975    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1976    Board oldBoard;
1977
1978    startedFromPositionFile = FALSE;
1979    if(gameInfo.variant == newVariant) return;
1980
1981    /* [HGM] This routine is called each time an assignment is made to
1982     * gameInfo.variant during a game, to make sure the board sizes
1983     * are set to match the new variant. If that means adding or deleting
1984     * holdings, we shift the playing board accordingly
1985     * This kludge is needed because in ICS observe mode, we get boards
1986     * of an ongoing game without knowing the variant, and learn about the
1987     * latter only later. This can be because of the move list we requested,
1988     * in which case the game history is refilled from the beginning anyway,
1989     * but also when receiving holdings of a crazyhouse game. In the latter
1990     * case we want to add those holdings to the already received position.
1991     */
1992
1993    
1994    if (appData.debugMode) {
1995      fprintf(debugFP, "Switch board from %s to %s\n",
1996              VariantName(gameInfo.variant), VariantName(newVariant));
1997      setbuf(debugFP, NULL);
1998    }
1999    shuffleOpenings = 0;       /* [HGM] shuffle */
2000    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2001    switch(newVariant) 
2002      {
2003      case VariantShogi:
2004        newWidth = 9;  newHeight = 9;
2005        gameInfo.holdingsSize = 7;
2006      case VariantBughouse:
2007      case VariantCrazyhouse:
2008        newHoldingsWidth = 2; break;
2009      case VariantGreat:
2010        newWidth = 10;
2011      case VariantSuper:
2012        newHoldingsWidth = 2;
2013        gameInfo.holdingsSize = 8;
2014        break;
2015      case VariantGothic:
2016      case VariantCapablanca:
2017      case VariantCapaRandom:
2018        newWidth = 10;
2019      default:
2020        newHoldingsWidth = gameInfo.holdingsSize = 0;
2021      };
2022    
2023    if(newWidth  != gameInfo.boardWidth  ||
2024       newHeight != gameInfo.boardHeight ||
2025       newHoldingsWidth != gameInfo.holdingsWidth ) {
2026      
2027      /* shift position to new playing area, if needed */
2028      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2029        for(i=0; i<BOARD_HEIGHT; i++) 
2030          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2031            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2032              board[i][j];
2033        for(i=0; i<newHeight; i++) {
2034          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2035          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2036        }
2037      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2038        for(i=0; i<BOARD_HEIGHT; i++)
2039          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2040            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2041              board[i][j];
2042      }
2043      gameInfo.boardWidth  = newWidth;
2044      gameInfo.boardHeight = newHeight;
2045      gameInfo.holdingsWidth = newHoldingsWidth;
2046      gameInfo.variant = newVariant;
2047      InitDrawingSizes(-2, 0);
2048    } else gameInfo.variant = newVariant;
2049    CopyBoard(oldBoard, board);   // remember correctly formatted board
2050      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2051    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2052 }
2053
2054 static int loggedOn = FALSE;
2055
2056 /*-- Game start info cache: --*/
2057 int gs_gamenum;
2058 char gs_kind[MSG_SIZ];
2059 static char player1Name[128] = "";
2060 static char player2Name[128] = "";
2061 static char cont_seq[] = "\n\\   ";
2062 static int player1Rating = -1;
2063 static int player2Rating = -1;
2064 /*----------------------------*/
2065
2066 ColorClass curColor = ColorNormal;
2067 int suppressKibitz = 0;
2068
2069 // [HGM] seekgraph
2070 Boolean soughtPending = FALSE;
2071 Boolean seekGraphUp;
2072 #define MAX_SEEK_ADS 200
2073 #define SQUARE 0x80
2074 char *seekAdList[MAX_SEEK_ADS];
2075 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2076 float tcList[MAX_SEEK_ADS];
2077 char colorList[MAX_SEEK_ADS];
2078 int nrOfSeekAds = 0;
2079 int minRating = 1010, maxRating = 2800;
2080 int hMargin = 10, vMargin = 20, h, w;
2081 extern int squareSize, lineGap;
2082
2083 void
2084 PlotSeekAd(int i)
2085 {
2086         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2087         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2088         if(r < minRating+100 && r >=0 ) r = minRating+100;
2089         if(r > maxRating) r = maxRating;
2090         if(tc < 1.) tc = 1.;
2091         if(tc > 95.) tc = 95.;
2092         x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2093         y = ((double)r - minRating)/(maxRating - minRating)
2094             * (h-vMargin-squareSize/8-1) + vMargin;
2095         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2096         if(strstr(seekAdList[i], " u ")) color = 1;
2097         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2098            !strstr(seekAdList[i], "bullet") &&
2099            !strstr(seekAdList[i], "blitz") &&
2100            !strstr(seekAdList[i], "standard") ) color = 2;
2101         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2102         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2103 }
2104
2105 void
2106 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2107 {
2108         char buf[MSG_SIZ], *ext = "";
2109         VariantClass v = StringToVariant(type);
2110         if(strstr(type, "wild")) {
2111             ext = type + 4; // append wild number
2112             if(v == VariantFischeRandom) type = "chess960"; else
2113             if(v == VariantLoadable) type = "setup"; else
2114             type = VariantName(v);
2115         }
2116         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2117         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2118             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2119             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2120             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2121             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2122             seekNrList[nrOfSeekAds] = nr;
2123             zList[nrOfSeekAds] = 0;
2124             seekAdList[nrOfSeekAds++] = StrSave(buf);
2125             if(plot) PlotSeekAd(nrOfSeekAds-1);
2126         }
2127 }
2128
2129 void
2130 EraseSeekDot(int i)
2131 {
2132     int x = xList[i], y = yList[i], d=squareSize/4, k;
2133     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2134     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2135     // now replot every dot that overlapped
2136     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2137         int xx = xList[k], yy = yList[k];
2138         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2139             DrawSeekDot(xx, yy, colorList[k]);
2140     }
2141 }
2142
2143 void
2144 RemoveSeekAd(int nr)
2145 {
2146         int i;
2147         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2148             EraseSeekDot(i);
2149             if(seekAdList[i]) free(seekAdList[i]);
2150             seekAdList[i] = seekAdList[--nrOfSeekAds];
2151             seekNrList[i] = seekNrList[nrOfSeekAds];
2152             ratingList[i] = ratingList[nrOfSeekAds];
2153             colorList[i]  = colorList[nrOfSeekAds];
2154             tcList[i] = tcList[nrOfSeekAds];
2155             xList[i]  = xList[nrOfSeekAds];
2156             yList[i]  = yList[nrOfSeekAds];
2157             zList[i]  = zList[nrOfSeekAds];
2158             seekAdList[nrOfSeekAds] = NULL;
2159             break;
2160         }
2161 }
2162
2163 Boolean
2164 MatchSoughtLine(char *line)
2165 {
2166     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2167     int nr, base, inc, u=0; char dummy;
2168
2169     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2170        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2171        (u=1) &&
2172        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2173         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2174         // match: compact and save the line
2175         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2176         return TRUE;
2177     }
2178     return FALSE;
2179 }
2180
2181 int
2182 DrawSeekGraph()
2183 {
2184     if(!seekGraphUp) return FALSE;
2185     int i;
2186     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2187     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2188
2189     DrawSeekBackground(0, 0, w, h);
2190     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2191     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2192     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2193         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2194         yy = h-1-yy;
2195         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2196         if(i%500 == 0) {
2197             char buf[MSG_SIZ];
2198             sprintf(buf, "%d", i);
2199             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2200         }
2201     }
2202     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2203     for(i=1; i<100; i+=(i<10?1:5)) {
2204         int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2205         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2206         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2207             char buf[MSG_SIZ];
2208             sprintf(buf, "%d", i);
2209             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2210         }
2211     }
2212     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2213     return TRUE;
2214 }
2215
2216 int SeekGraphClick(ClickType click, int x, int y, int moving)
2217 {
2218     static int lastDown = 0, displayed = 0, lastSecond;
2219     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2220         if(click == Release || moving) return FALSE;
2221         nrOfSeekAds = 0;
2222         soughtPending = TRUE;
2223         SendToICS(ics_prefix);
2224         SendToICS("sought\n"); // should this be "sought all"?
2225     } else { // issue challenge based on clicked ad
2226         int dist = 10000; int i, closest = 0, second = 0;
2227         for(i=0; i<nrOfSeekAds; i++) {
2228             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2229             if(d < dist) { dist = d; closest = i; }
2230             second += (d - zList[i] < 120); // count in-range ads
2231             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2232         }
2233         if(dist < 120) {
2234             char buf[MSG_SIZ];
2235             second = (second > 1);
2236             if(displayed != closest || second != lastSecond) {
2237                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2238                 lastSecond = second; displayed = closest;
2239             }
2240             if(click == Press) {
2241                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2242                 lastDown = closest;
2243                 return TRUE;
2244             } // on press 'hit', only show info
2245             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2246             sprintf(buf, "play %d\n", seekNrList[closest]);
2247             SendToICS(ics_prefix);
2248             SendToICS(buf);
2249             return TRUE; // let incoming board of started game pop down the graph
2250         } else if(click == Release) { // release 'miss' is ignored
2251             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2252             if(moving == 2) { // right up-click
2253                 nrOfSeekAds = 0; // refresh graph
2254                 soughtPending = TRUE;
2255                 SendToICS(ics_prefix);
2256                 SendToICS("sought\n"); // should this be "sought all"?
2257             }
2258             return TRUE;
2259         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2260         // press miss or release hit 'pop down' seek graph
2261         seekGraphUp = FALSE;
2262         DrawPosition(TRUE, NULL);
2263     }
2264     return TRUE;
2265 }
2266
2267 void
2268 read_from_ics(isr, closure, data, count, error)
2269      InputSourceRef isr;
2270      VOIDSTAR closure;
2271      char *data;
2272      int count;
2273      int error;
2274 {
2275 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2276 #define STARTED_NONE 0
2277 #define STARTED_MOVES 1
2278 #define STARTED_BOARD 2
2279 #define STARTED_OBSERVE 3
2280 #define STARTED_HOLDINGS 4
2281 #define STARTED_CHATTER 5
2282 #define STARTED_COMMENT 6
2283 #define STARTED_MOVES_NOHIDE 7
2284     
2285     static int started = STARTED_NONE;
2286     static char parse[20000];
2287     static int parse_pos = 0;
2288     static char buf[BUF_SIZE + 1];
2289     static int firstTime = TRUE, intfSet = FALSE;
2290     static ColorClass prevColor = ColorNormal;
2291     static int savingComment = FALSE;
2292     static int cmatch = 0; // continuation sequence match
2293     char *bp;
2294     char str[500];
2295     int i, oldi;
2296     int buf_len;
2297     int next_out;
2298     int tkind;
2299     int backup;    /* [DM] For zippy color lines */
2300     char *p;
2301     char talker[MSG_SIZ]; // [HGM] chat
2302     int channel;
2303
2304     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2305
2306     if (appData.debugMode) {
2307       if (!error) {
2308         fprintf(debugFP, "<ICS: ");
2309         show_bytes(debugFP, data, count);
2310         fprintf(debugFP, "\n");
2311       }
2312     }
2313
2314     if (appData.debugMode) { int f = forwardMostMove;
2315         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2316                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2317                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2318     }
2319     if (count > 0) {
2320         /* If last read ended with a partial line that we couldn't parse,
2321            prepend it to the new read and try again. */
2322         if (leftover_len > 0) {
2323             for (i=0; i<leftover_len; i++)
2324               buf[i] = buf[leftover_start + i];
2325         }
2326
2327     /* copy new characters into the buffer */
2328     bp = buf + leftover_len;
2329     buf_len=leftover_len;
2330     for (i=0; i<count; i++)
2331     {
2332         // ignore these
2333         if (data[i] == '\r')
2334             continue;
2335
2336         // join lines split by ICS?
2337         if (!appData.noJoin)
2338         {
2339             /*
2340                 Joining just consists of finding matches against the
2341                 continuation sequence, and discarding that sequence
2342                 if found instead of copying it.  So, until a match
2343                 fails, there's nothing to do since it might be the
2344                 complete sequence, and thus, something we don't want
2345                 copied.
2346             */
2347             if (data[i] == cont_seq[cmatch])
2348             {
2349                 cmatch++;
2350                 if (cmatch == strlen(cont_seq))
2351                 {
2352                     cmatch = 0; // complete match.  just reset the counter
2353
2354                     /*
2355                         it's possible for the ICS to not include the space
2356                         at the end of the last word, making our [correct]
2357                         join operation fuse two separate words.  the server
2358                         does this when the space occurs at the width setting.
2359                     */
2360                     if (!buf_len || buf[buf_len-1] != ' ')
2361                     {
2362                         *bp++ = ' ';
2363                         buf_len++;
2364                     }
2365                 }
2366                 continue;
2367             }
2368             else if (cmatch)
2369             {
2370                 /*
2371                     match failed, so we have to copy what matched before
2372                     falling through and copying this character.  In reality,
2373                     this will only ever be just the newline character, but
2374                     it doesn't hurt to be precise.
2375                 */
2376                 strncpy(bp, cont_seq, cmatch);
2377                 bp += cmatch;
2378                 buf_len += cmatch;
2379                 cmatch = 0;
2380             }
2381         }
2382
2383         // copy this char
2384         *bp++ = data[i];
2385         buf_len++;
2386     }
2387
2388         buf[buf_len] = NULLCHAR;
2389 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2390         next_out = 0;
2391         leftover_start = 0;
2392         
2393         i = 0;
2394         while (i < buf_len) {
2395             /* Deal with part of the TELNET option negotiation
2396                protocol.  We refuse to do anything beyond the
2397                defaults, except that we allow the WILL ECHO option,
2398                which ICS uses to turn off password echoing when we are
2399                directly connected to it.  We reject this option
2400                if localLineEditing mode is on (always on in xboard)
2401                and we are talking to port 23, which might be a real
2402                telnet server that will try to keep WILL ECHO on permanently.
2403              */
2404             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2405                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2406                 unsigned char option;
2407                 oldi = i;
2408                 switch ((unsigned char) buf[++i]) {
2409                   case TN_WILL:
2410                     if (appData.debugMode)
2411                       fprintf(debugFP, "\n<WILL ");
2412                     switch (option = (unsigned char) buf[++i]) {
2413                       case TN_ECHO:
2414                         if (appData.debugMode)
2415                           fprintf(debugFP, "ECHO ");
2416                         /* Reply only if this is a change, according
2417                            to the protocol rules. */
2418                         if (remoteEchoOption) break;
2419                         if (appData.localLineEditing &&
2420                             atoi(appData.icsPort) == TN_PORT) {
2421                             TelnetRequest(TN_DONT, TN_ECHO);
2422                         } else {
2423                             EchoOff();
2424                             TelnetRequest(TN_DO, TN_ECHO);
2425                             remoteEchoOption = TRUE;
2426                         }
2427                         break;
2428                       default:
2429                         if (appData.debugMode)
2430                           fprintf(debugFP, "%d ", option);
2431                         /* Whatever this is, we don't want it. */
2432                         TelnetRequest(TN_DONT, option);
2433                         break;
2434                     }
2435                     break;
2436                   case TN_WONT:
2437                     if (appData.debugMode)
2438                       fprintf(debugFP, "\n<WONT ");
2439                     switch (option = (unsigned char) buf[++i]) {
2440                       case TN_ECHO:
2441                         if (appData.debugMode)
2442                           fprintf(debugFP, "ECHO ");
2443                         /* Reply only if this is a change, according
2444                            to the protocol rules. */
2445                         if (!remoteEchoOption) break;
2446                         EchoOn();
2447                         TelnetRequest(TN_DONT, TN_ECHO);
2448                         remoteEchoOption = FALSE;
2449                         break;
2450                       default:
2451                         if (appData.debugMode)
2452                           fprintf(debugFP, "%d ", (unsigned char) option);
2453                         /* Whatever this is, it must already be turned
2454                            off, because we never agree to turn on
2455                            anything non-default, so according to the
2456                            protocol rules, we don't reply. */
2457                         break;
2458                     }
2459                     break;
2460                   case TN_DO:
2461                     if (appData.debugMode)
2462                       fprintf(debugFP, "\n<DO ");
2463                     switch (option = (unsigned char) buf[++i]) {
2464                       default:
2465                         /* Whatever this is, we refuse to do it. */
2466                         if (appData.debugMode)
2467                           fprintf(debugFP, "%d ", option);
2468                         TelnetRequest(TN_WONT, option);
2469                         break;
2470                     }
2471                     break;
2472                   case TN_DONT:
2473                     if (appData.debugMode)
2474                       fprintf(debugFP, "\n<DONT ");
2475                     switch (option = (unsigned char) buf[++i]) {
2476                       default:
2477                         if (appData.debugMode)
2478                           fprintf(debugFP, "%d ", option);
2479                         /* Whatever this is, we are already not doing
2480                            it, because we never agree to do anything
2481                            non-default, so according to the protocol
2482                            rules, we don't reply. */
2483                         break;
2484                     }
2485                     break;
2486                   case TN_IAC:
2487                     if (appData.debugMode)
2488                       fprintf(debugFP, "\n<IAC ");
2489                     /* Doubled IAC; pass it through */
2490                     i--;
2491                     break;
2492                   default:
2493                     if (appData.debugMode)
2494                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2495                     /* Drop all other telnet commands on the floor */
2496                     break;
2497                 }
2498                 if (oldi > next_out)
2499                   SendToPlayer(&buf[next_out], oldi - next_out);
2500                 if (++i > next_out)
2501                   next_out = i;
2502                 continue;
2503             }
2504                 
2505             /* OK, this at least will *usually* work */
2506             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2507                 loggedOn = TRUE;
2508             }
2509             
2510             if (loggedOn && !intfSet) {
2511                 if (ics_type == ICS_ICC) {
2512                   sprintf(str,
2513                           "/set-quietly interface %s\n/set-quietly style 12\n",
2514                           programVersion);
2515                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2516                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2517                 } else if (ics_type == ICS_CHESSNET) {
2518                   sprintf(str, "/style 12\n");
2519                 } else {
2520                   strcpy(str, "alias $ @\n$set interface ");
2521                   strcat(str, programVersion);
2522                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2523                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2524                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2525 #ifdef WIN32
2526                   strcat(str, "$iset nohighlight 1\n");
2527 #endif
2528                   strcat(str, "$iset lock 1\n$style 12\n");
2529                 }
2530                 SendToICS(str);
2531                 NotifyFrontendLogin();
2532                 intfSet = TRUE;
2533             }
2534
2535             if (started == STARTED_COMMENT) {
2536                 /* Accumulate characters in comment */
2537                 parse[parse_pos++] = buf[i];
2538                 if (buf[i] == '\n') {
2539                     parse[parse_pos] = NULLCHAR;
2540                     if(chattingPartner>=0) {
2541                         char mess[MSG_SIZ];
2542                         sprintf(mess, "%s%s", talker, parse);
2543                         OutputChatMessage(chattingPartner, mess);
2544                         chattingPartner = -1;
2545                     } else
2546                     if(!suppressKibitz) // [HGM] kibitz
2547                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2548                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2549                         int nrDigit = 0, nrAlph = 0, j;
2550                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2551                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2552                         parse[parse_pos] = NULLCHAR;
2553                         // try to be smart: if it does not look like search info, it should go to
2554                         // ICS interaction window after all, not to engine-output window.
2555                         for(j=0; j<parse_pos; j++) { // count letters and digits
2556                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2557                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2558                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2559                         }
2560                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2561                             int depth=0; float score;
2562                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2563                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2564                                 pvInfoList[forwardMostMove-1].depth = depth;
2565                                 pvInfoList[forwardMostMove-1].score = 100*score;
2566                             }
2567                             OutputKibitz(suppressKibitz, parse);
2568                             next_out = i+1; // [HGM] suppress printing in ICS window
2569                         } else {
2570                             char tmp[MSG_SIZ];
2571                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2572                             SendToPlayer(tmp, strlen(tmp));
2573                         }
2574                     }
2575                     started = STARTED_NONE;
2576                 } else {
2577                     /* Don't match patterns against characters in comment */
2578                     i++;
2579                     continue;
2580                 }
2581             }
2582             if (started == STARTED_CHATTER) {
2583                 if (buf[i] != '\n') {
2584                     /* Don't match patterns against characters in chatter */
2585                     i++;
2586                     continue;
2587                 }
2588                 started = STARTED_NONE;
2589             }
2590
2591             /* Kludge to deal with rcmd protocol */
2592             if (firstTime && looking_at(buf, &i, "\001*")) {
2593                 DisplayFatalError(&buf[1], 0, 1);
2594                 continue;
2595             } else {
2596                 firstTime = FALSE;
2597             }
2598
2599             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2600                 ics_type = ICS_ICC;
2601                 ics_prefix = "/";
2602                 if (appData.debugMode)
2603                   fprintf(debugFP, "ics_type %d\n", ics_type);
2604                 continue;
2605             }
2606             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2607                 ics_type = ICS_FICS;
2608                 ics_prefix = "$";
2609                 if (appData.debugMode)
2610                   fprintf(debugFP, "ics_type %d\n", ics_type);
2611                 continue;
2612             }
2613             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2614                 ics_type = ICS_CHESSNET;
2615                 ics_prefix = "/";
2616                 if (appData.debugMode)
2617                   fprintf(debugFP, "ics_type %d\n", ics_type);
2618                 continue;
2619             }
2620
2621             if (!loggedOn &&
2622                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2623                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2624                  looking_at(buf, &i, "will be \"*\""))) {
2625               strcpy(ics_handle, star_match[0]);
2626               continue;
2627             }
2628
2629             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2630               char buf[MSG_SIZ];
2631               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2632               DisplayIcsInteractionTitle(buf);
2633               have_set_title = TRUE;
2634             }
2635
2636             /* skip finger notes */
2637             if (started == STARTED_NONE &&
2638                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2639                  (buf[i] == '1' && buf[i+1] == '0')) &&
2640                 buf[i+2] == ':' && buf[i+3] == ' ') {
2641               started = STARTED_CHATTER;
2642               i += 3;
2643               continue;
2644             }
2645
2646             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2647             if(appData.seekGraph) {
2648                 if(soughtPending && MatchSoughtLine(buf+i)) {
2649                     i = strstr(buf+i, "rated") - buf;
2650                     next_out = leftover_start = i;
2651                     started = STARTED_CHATTER;
2652                     suppressKibitz = TRUE;
2653                     continue;
2654                 }
2655                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2656                         && looking_at(buf, &i, "* ads displayed")) {
2657                     soughtPending = FALSE;
2658                     seekGraphUp = TRUE;
2659                     DrawSeekGraph();
2660                     continue;
2661                 }
2662                 if(appData.autoRefresh) {
2663                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2664                         int s = (ics_type == ICS_ICC); // ICC format differs
2665                         if(seekGraphUp)
2666                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2667                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2668                         looking_at(buf, &i, "*% "); // eat prompt
2669                         next_out = i; // suppress
2670                         continue;
2671                     }
2672                     if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2673                         char *p = star_match[0];
2674                         while(*p) {
2675                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2676                             while(*p && *p++ != ' '); // next
2677                         }
2678                         looking_at(buf, &i, "*% "); // eat prompt
2679                         next_out = i;
2680                         continue;
2681                     }
2682                 }
2683             }
2684
2685             /* skip formula vars */
2686             if (started == STARTED_NONE &&
2687                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2688               started = STARTED_CHATTER;
2689               i += 3;
2690               continue;
2691             }
2692
2693             oldi = i;
2694             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2695             if (appData.autoKibitz && started == STARTED_NONE && 
2696                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2697                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2698                 if(looking_at(buf, &i, "* kibitzes: ") &&
2699                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2700                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2701                         suppressKibitz = TRUE;
2702                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2703                                 && (gameMode == IcsPlayingWhite)) ||
2704                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2705                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2706                             started = STARTED_CHATTER; // own kibitz we simply discard
2707                         else {
2708                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2709                             parse_pos = 0; parse[0] = NULLCHAR;
2710                             savingComment = TRUE;
2711                             suppressKibitz = gameMode != IcsObserving ? 2 :
2712                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2713                         } 
2714                         continue;
2715                 } else
2716                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2717                     // suppress the acknowledgements of our own autoKibitz
2718                     char *p;
2719                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2720                     SendToPlayer(star_match[0], strlen(star_match[0]));
2721                     looking_at(buf, &i, "*% "); // eat prompt
2722                     next_out = i;
2723                 }
2724             } // [HGM] kibitz: end of patch
2725
2726 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2727
2728             // [HGM] chat: intercept tells by users for which we have an open chat window
2729             channel = -1;
2730             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2731                                            looking_at(buf, &i, "* whispers:") ||
2732                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2733                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2734                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2735                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2736                 int p;
2737                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2738                 chattingPartner = -1;
2739
2740                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2741                 for(p=0; p<MAX_CHAT; p++) {
2742                     if(channel == atoi(chatPartner[p])) {
2743                     talker[0] = '['; strcat(talker, "] ");
2744                     chattingPartner = p; break;
2745                     }
2746                 } else
2747                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2748                 for(p=0; p<MAX_CHAT; p++) {
2749                     if(!strcmp("WHISPER", chatPartner[p])) {
2750                         talker[0] = '['; strcat(talker, "] ");
2751                         chattingPartner = p; break;
2752                     }
2753                 }
2754                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2755                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2756                     talker[0] = 0;
2757                     chattingPartner = p; break;
2758                 }
2759                 if(chattingPartner<0) i = oldi; else {
2760                     started = STARTED_COMMENT;
2761                     parse_pos = 0; parse[0] = NULLCHAR;
2762                     savingComment = 3 + chattingPartner; // counts as TRUE
2763                     suppressKibitz = TRUE;
2764                 }
2765             } // [HGM] chat: end of patch
2766
2767             if (appData.zippyTalk || appData.zippyPlay) {
2768                 /* [DM] Backup address for color zippy lines */
2769                 backup = i;
2770 #if ZIPPY
2771        #ifdef WIN32
2772                if (loggedOn == TRUE)
2773                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2774                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2775        #else
2776                 if (ZippyControl(buf, &i) ||
2777                     ZippyConverse(buf, &i) ||
2778                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2779                       loggedOn = TRUE;
2780                       if (!appData.colorize) continue;
2781                 }
2782        #endif
2783 #endif
2784             } // [DM] 'else { ' deleted
2785                 if (
2786                     /* Regular tells and says */
2787                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2788                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2789                     looking_at(buf, &i, "* says: ") ||
2790                     /* Don't color "message" or "messages" output */
2791                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2792                     looking_at(buf, &i, "*. * at *:*: ") ||
2793                     looking_at(buf, &i, "--* (*:*): ") ||
2794                     /* Message notifications (same color as tells) */
2795                     looking_at(buf, &i, "* has left a message ") ||
2796                     looking_at(buf, &i, "* just sent you a message:\n") ||
2797                     /* Whispers and kibitzes */
2798                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2799                     looking_at(buf, &i, "* kibitzes: ") ||
2800                     /* Channel tells */
2801                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2802
2803                   if (tkind == 1 && strchr(star_match[0], ':')) {
2804                       /* Avoid "tells you:" spoofs in channels */
2805                      tkind = 3;
2806                   }
2807                   if (star_match[0][0] == NULLCHAR ||
2808                       strchr(star_match[0], ' ') ||
2809                       (tkind == 3 && strchr(star_match[1], ' '))) {
2810                     /* Reject bogus matches */
2811                     i = oldi;
2812                   } else {
2813                     if (appData.colorize) {
2814                       if (oldi > next_out) {
2815                         SendToPlayer(&buf[next_out], oldi - next_out);
2816                         next_out = oldi;
2817                       }
2818                       switch (tkind) {
2819                       case 1:
2820                         Colorize(ColorTell, FALSE);
2821                         curColor = ColorTell;
2822                         break;
2823                       case 2:
2824                         Colorize(ColorKibitz, FALSE);
2825                         curColor = ColorKibitz;
2826                         break;
2827                       case 3:
2828                         p = strrchr(star_match[1], '(');
2829                         if (p == NULL) {
2830                           p = star_match[1];
2831                         } else {
2832                           p++;
2833                         }
2834                         if (atoi(p) == 1) {
2835                           Colorize(ColorChannel1, FALSE);
2836                           curColor = ColorChannel1;
2837                         } else {
2838                           Colorize(ColorChannel, FALSE);
2839                           curColor = ColorChannel;
2840                         }
2841                         break;
2842                       case 5:
2843                         curColor = ColorNormal;
2844                         break;
2845                       }
2846                     }
2847                     if (started == STARTED_NONE && appData.autoComment &&
2848                         (gameMode == IcsObserving ||
2849                          gameMode == IcsPlayingWhite ||
2850                          gameMode == IcsPlayingBlack)) {
2851                       parse_pos = i - oldi;
2852                       memcpy(parse, &buf[oldi], parse_pos);
2853                       parse[parse_pos] = NULLCHAR;
2854                       started = STARTED_COMMENT;
2855                       savingComment = TRUE;
2856                     } else {
2857                       started = STARTED_CHATTER;
2858                       savingComment = FALSE;
2859                     }
2860                     loggedOn = TRUE;
2861                     continue;
2862                   }
2863                 }
2864
2865                 if (looking_at(buf, &i, "* s-shouts: ") ||
2866                     looking_at(buf, &i, "* c-shouts: ")) {
2867                     if (appData.colorize) {
2868                         if (oldi > next_out) {
2869                             SendToPlayer(&buf[next_out], oldi - next_out);
2870                             next_out = oldi;
2871                         }
2872                         Colorize(ColorSShout, FALSE);
2873                         curColor = ColorSShout;
2874                     }
2875                     loggedOn = TRUE;
2876                     started = STARTED_CHATTER;
2877                     continue;
2878                 }
2879
2880                 if (looking_at(buf, &i, "--->")) {
2881                     loggedOn = TRUE;
2882                     continue;
2883                 }
2884
2885                 if (looking_at(buf, &i, "* shouts: ") ||
2886                     looking_at(buf, &i, "--> ")) {
2887                     if (appData.colorize) {
2888                         if (oldi > next_out) {
2889                             SendToPlayer(&buf[next_out], oldi - next_out);
2890                             next_out = oldi;
2891                         }
2892                         Colorize(ColorShout, FALSE);
2893                         curColor = ColorShout;
2894                     }
2895                     loggedOn = TRUE;
2896                     started = STARTED_CHATTER;
2897                     continue;
2898                 }
2899
2900                 if (looking_at( buf, &i, "Challenge:")) {
2901                     if (appData.colorize) {
2902                         if (oldi > next_out) {
2903                             SendToPlayer(&buf[next_out], oldi - next_out);
2904                             next_out = oldi;
2905                         }
2906                         Colorize(ColorChallenge, FALSE);
2907                         curColor = ColorChallenge;
2908                     }
2909                     loggedOn = TRUE;
2910                     continue;
2911                 }
2912
2913                 if (looking_at(buf, &i, "* offers you") ||
2914                     looking_at(buf, &i, "* offers to be") ||
2915                     looking_at(buf, &i, "* would like to") ||
2916                     looking_at(buf, &i, "* requests to") ||
2917                     looking_at(buf, &i, "Your opponent offers") ||
2918                     looking_at(buf, &i, "Your opponent requests")) {
2919
2920                     if (appData.colorize) {
2921                         if (oldi > next_out) {
2922                             SendToPlayer(&buf[next_out], oldi - next_out);
2923                             next_out = oldi;
2924                         }
2925                         Colorize(ColorRequest, FALSE);
2926                         curColor = ColorRequest;
2927                     }
2928                     continue;
2929                 }
2930
2931                 if (looking_at(buf, &i, "* (*) seeking")) {
2932                     if (appData.colorize) {
2933                         if (oldi > next_out) {
2934                             SendToPlayer(&buf[next_out], oldi - next_out);
2935                             next_out = oldi;
2936                         }
2937                         Colorize(ColorSeek, FALSE);
2938                         curColor = ColorSeek;
2939                     }
2940                     continue;
2941             }
2942
2943             if (looking_at(buf, &i, "\\   ")) {
2944                 if (prevColor != ColorNormal) {
2945                     if (oldi > next_out) {
2946                         SendToPlayer(&buf[next_out], oldi - next_out);
2947                         next_out = oldi;
2948                     }
2949                     Colorize(prevColor, TRUE);
2950                     curColor = prevColor;
2951                 }
2952                 if (savingComment) {
2953                     parse_pos = i - oldi;
2954                     memcpy(parse, &buf[oldi], parse_pos);
2955                     parse[parse_pos] = NULLCHAR;
2956                     started = STARTED_COMMENT;
2957                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2958                         chattingPartner = savingComment - 3; // kludge to remember the box
2959                 } else {
2960                     started = STARTED_CHATTER;
2961                 }
2962                 continue;
2963             }
2964
2965             if (looking_at(buf, &i, "Black Strength :") ||
2966                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2967                 looking_at(buf, &i, "<10>") ||
2968                 looking_at(buf, &i, "#@#")) {
2969                 /* Wrong board style */
2970                 loggedOn = TRUE;
2971                 SendToICS(ics_prefix);
2972                 SendToICS("set style 12\n");
2973                 SendToICS(ics_prefix);
2974                 SendToICS("refresh\n");
2975                 continue;
2976             }
2977             
2978             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2979                 ICSInitScript();
2980                 have_sent_ICS_logon = 1;
2981                 continue;
2982             }
2983               
2984             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2985                 (looking_at(buf, &i, "\n<12> ") ||
2986                  looking_at(buf, &i, "<12> "))) {
2987                 loggedOn = TRUE;
2988                 if (oldi > next_out) {
2989                     SendToPlayer(&buf[next_out], oldi - next_out);
2990                 }
2991                 next_out = i;
2992                 started = STARTED_BOARD;
2993                 parse_pos = 0;
2994                 continue;
2995             }
2996
2997             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2998                 looking_at(buf, &i, "<b1> ")) {
2999                 if (oldi > next_out) {
3000                     SendToPlayer(&buf[next_out], oldi - next_out);
3001                 }
3002                 next_out = i;
3003                 started = STARTED_HOLDINGS;
3004                 parse_pos = 0;
3005                 continue;
3006             }
3007
3008             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3009                 loggedOn = TRUE;
3010                 /* Header for a move list -- first line */
3011
3012                 switch (ics_getting_history) {
3013                   case H_FALSE:
3014                     switch (gameMode) {
3015                       case IcsIdle:
3016                       case BeginningOfGame:
3017                         /* User typed "moves" or "oldmoves" while we
3018                            were idle.  Pretend we asked for these
3019                            moves and soak them up so user can step
3020                            through them and/or save them.
3021                            */
3022                         Reset(FALSE, TRUE);
3023                         gameMode = IcsObserving;
3024                         ModeHighlight();
3025                         ics_gamenum = -1;
3026                         ics_getting_history = H_GOT_UNREQ_HEADER;
3027                         break;
3028                       case EditGame: /*?*/
3029                       case EditPosition: /*?*/
3030                         /* Should above feature work in these modes too? */
3031                         /* For now it doesn't */
3032                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3033                         break;
3034                       default:
3035                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3036                         break;
3037                     }
3038                     break;
3039                   case H_REQUESTED:
3040                     /* Is this the right one? */
3041                     if (gameInfo.white && gameInfo.black &&
3042                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3043                         strcmp(gameInfo.black, star_match[2]) == 0) {
3044                         /* All is well */
3045                         ics_getting_history = H_GOT_REQ_HEADER;
3046                     }
3047                     break;
3048                   case H_GOT_REQ_HEADER:
3049                   case H_GOT_UNREQ_HEADER:
3050                   case H_GOT_UNWANTED_HEADER:
3051                   case H_GETTING_MOVES:
3052                     /* Should not happen */
3053                     DisplayError(_("Error gathering move list: two headers"), 0);
3054                     ics_getting_history = H_FALSE;
3055                     break;
3056                 }
3057
3058                 /* Save player ratings into gameInfo if needed */
3059                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3060                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3061                     (gameInfo.whiteRating == -1 ||
3062                      gameInfo.blackRating == -1)) {
3063
3064                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3065                     gameInfo.blackRating = string_to_rating(star_match[3]);
3066                     if (appData.debugMode)
3067                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3068                               gameInfo.whiteRating, gameInfo.blackRating);
3069                 }
3070                 continue;
3071             }
3072
3073             if (looking_at(buf, &i,
3074               "* * match, initial time: * minute*, increment: * second")) {
3075                 /* Header for a move list -- second line */
3076                 /* Initial board will follow if this is a wild game */
3077                 if (gameInfo.event != NULL) free(gameInfo.event);
3078                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3079                 gameInfo.event = StrSave(str);
3080                 /* [HGM] we switched variant. Translate boards if needed. */
3081                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3082                 continue;
3083             }
3084
3085             if (looking_at(buf, &i, "Move  ")) {
3086                 /* Beginning of a move list */
3087                 switch (ics_getting_history) {
3088                   case H_FALSE:
3089                     /* Normally should not happen */
3090                     /* Maybe user hit reset while we were parsing */
3091                     break;
3092                   case H_REQUESTED:
3093                     /* Happens if we are ignoring a move list that is not
3094                      * the one we just requested.  Common if the user
3095                      * tries to observe two games without turning off
3096                      * getMoveList */
3097                     break;
3098                   case H_GETTING_MOVES:
3099                     /* Should not happen */
3100                     DisplayError(_("Error gathering move list: nested"), 0);
3101                     ics_getting_history = H_FALSE;
3102                     break;
3103                   case H_GOT_REQ_HEADER:
3104                     ics_getting_history = H_GETTING_MOVES;
3105                     started = STARTED_MOVES;
3106                     parse_pos = 0;
3107                     if (oldi > next_out) {
3108                         SendToPlayer(&buf[next_out], oldi - next_out);
3109                     }
3110                     break;
3111                   case H_GOT_UNREQ_HEADER:
3112                     ics_getting_history = H_GETTING_MOVES;
3113                     started = STARTED_MOVES_NOHIDE;
3114                     parse_pos = 0;
3115                     break;
3116                   case H_GOT_UNWANTED_HEADER:
3117                     ics_getting_history = H_FALSE;
3118                     break;
3119                 }
3120                 continue;
3121             }                           
3122             
3123             if (looking_at(buf, &i, "% ") ||
3124                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3125                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3126                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3127                     soughtPending = FALSE;
3128                     seekGraphUp = TRUE;
3129                     DrawSeekGraph();
3130                 }
3131                 if(suppressKibitz) next_out = i;
3132                 savingComment = FALSE;
3133                 suppressKibitz = 0;
3134                 switch (started) {
3135                   case STARTED_MOVES:
3136                   case STARTED_MOVES_NOHIDE:
3137                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3138                     parse[parse_pos + i - oldi] = NULLCHAR;
3139                     ParseGameHistory(parse);
3140 #if ZIPPY
3141                     if (appData.zippyPlay && first.initDone) {
3142                         FeedMovesToProgram(&first, forwardMostMove);
3143                         if (gameMode == IcsPlayingWhite) {
3144                             if (WhiteOnMove(forwardMostMove)) {
3145                                 if (first.sendTime) {
3146                                   if (first.useColors) {
3147                                     SendToProgram("black\n", &first); 
3148                                   }
3149                                   SendTimeRemaining(&first, TRUE);
3150                                 }
3151                                 if (first.useColors) {
3152                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3153                                 }
3154                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3155                                 first.maybeThinking = TRUE;
3156                             } else {
3157                                 if (first.usePlayother) {
3158                                   if (first.sendTime) {
3159                                     SendTimeRemaining(&first, TRUE);
3160                                   }
3161                                   SendToProgram("playother\n", &first);
3162                                   firstMove = FALSE;
3163                                 } else {
3164                                   firstMove = TRUE;
3165                                 }
3166                             }
3167                         } else if (gameMode == IcsPlayingBlack) {
3168                             if (!WhiteOnMove(forwardMostMove)) {
3169                                 if (first.sendTime) {
3170                                   if (first.useColors) {
3171                                     SendToProgram("white\n", &first);
3172                                   }
3173                                   SendTimeRemaining(&first, FALSE);
3174                                 }
3175                                 if (first.useColors) {
3176                                   SendToProgram("black\n", &first);
3177                                 }
3178                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3179                                 first.maybeThinking = TRUE;
3180                             } else {
3181                                 if (first.usePlayother) {
3182                                   if (first.sendTime) {
3183                                     SendTimeRemaining(&first, FALSE);
3184                                   }
3185                                   SendToProgram("playother\n", &first);
3186                                   firstMove = FALSE;
3187                                 } else {
3188                                   firstMove = TRUE;
3189                                 }
3190                             }
3191                         }                       
3192                     }
3193 #endif
3194                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3195                         /* Moves came from oldmoves or moves command
3196                            while we weren't doing anything else.
3197                            */
3198                         currentMove = forwardMostMove;
3199                         ClearHighlights();/*!!could figure this out*/
3200                         flipView = appData.flipView;
3201                         DrawPosition(TRUE, boards[currentMove]);
3202                         DisplayBothClocks();
3203                         sprintf(str, "%s vs. %s",
3204                                 gameInfo.white, gameInfo.black);
3205                         DisplayTitle(str);
3206                         gameMode = IcsIdle;
3207                     } else {
3208                         /* Moves were history of an active game */
3209                         if (gameInfo.resultDetails != NULL) {
3210                             free(gameInfo.resultDetails);
3211                             gameInfo.resultDetails = NULL;
3212                         }
3213                     }
3214                     HistorySet(parseList, backwardMostMove,
3215                                forwardMostMove, currentMove-1);
3216                     DisplayMove(currentMove - 1);
3217                     if (started == STARTED_MOVES) next_out = i;
3218                     started = STARTED_NONE;
3219                     ics_getting_history = H_FALSE;
3220                     break;
3221
3222                   case STARTED_OBSERVE:
3223                     started = STARTED_NONE;
3224                     SendToICS(ics_prefix);
3225                     SendToICS("refresh\n");
3226                     break;
3227
3228                   default:
3229                     break;
3230                 }
3231                 if(bookHit) { // [HGM] book: simulate book reply
3232                     static char bookMove[MSG_SIZ]; // a bit generous?
3233
3234                     programStats.nodes = programStats.depth = programStats.time = 
3235                     programStats.score = programStats.got_only_move = 0;
3236                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3237
3238                     strcpy(bookMove, "move ");
3239                     strcat(bookMove, bookHit);
3240                     HandleMachineMove(bookMove, &first);
3241                 }
3242                 continue;
3243             }
3244             
3245             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3246                  started == STARTED_HOLDINGS ||
3247                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3248                 /* Accumulate characters in move list or board */
3249                 parse[parse_pos++] = buf[i];
3250             }
3251             
3252             /* Start of game messages.  Mostly we detect start of game
3253                when the first board image arrives.  On some versions
3254                of the ICS, though, we need to do a "refresh" after starting
3255                to observe in order to get the current board right away. */
3256             if (looking_at(buf, &i, "Adding game * to observation list")) {
3257                 started = STARTED_OBSERVE;
3258                 continue;
3259             }
3260
3261             /* Handle auto-observe */
3262             if (appData.autoObserve &&
3263                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3264                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3265                 char *player;
3266                 /* Choose the player that was highlighted, if any. */
3267                 if (star_match[0][0] == '\033' ||
3268                     star_match[1][0] != '\033') {
3269                     player = star_match[0];
3270                 } else {
3271                     player = star_match[2];
3272                 }
3273                 sprintf(str, "%sobserve %s\n",
3274                         ics_prefix, StripHighlightAndTitle(player));
3275                 SendToICS(str);
3276
3277                 /* Save ratings from notify string */
3278                 strcpy(player1Name, star_match[0]);
3279                 player1Rating = string_to_rating(star_match[1]);
3280                 strcpy(player2Name, star_match[2]);
3281                 player2Rating = string_to_rating(star_match[3]);
3282
3283                 if (appData.debugMode)
3284                   fprintf(debugFP, 
3285                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3286                           player1Name, player1Rating,
3287                           player2Name, player2Rating);
3288
3289                 continue;
3290             }
3291
3292             /* Deal with automatic examine mode after a game,
3293                and with IcsObserving -> IcsExamining transition */
3294             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3295                 looking_at(buf, &i, "has made you an examiner of game *")) {
3296
3297                 int gamenum = atoi(star_match[0]);
3298                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3299                     gamenum == ics_gamenum) {
3300                     /* We were already playing or observing this game;
3301                        no need to refetch history */
3302                     gameMode = IcsExamining;
3303                     if (pausing) {
3304                         pauseExamForwardMostMove = forwardMostMove;
3305                     } else if (currentMove < forwardMostMove) {
3306                         ForwardInner(forwardMostMove);
3307                     }
3308                 } else {
3309                     /* I don't think this case really can happen */
3310                     SendToICS(ics_prefix);
3311                     SendToICS("refresh\n");
3312                 }
3313                 continue;
3314             }    
3315             
3316             /* Error messages */
3317 //          if (ics_user_moved) {
3318             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3319                 if (looking_at(buf, &i, "Illegal move") ||
3320                     looking_at(buf, &i, "Not a legal move") ||
3321                     looking_at(buf, &i, "Your king is in check") ||
3322                     looking_at(buf, &i, "It isn't your turn") ||
3323                     looking_at(buf, &i, "It is not your move")) {
3324                     /* Illegal move */
3325                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3326                         currentMove = forwardMostMove-1;
3327                         DisplayMove(currentMove - 1); /* before DMError */
3328                         DrawPosition(FALSE, boards[currentMove]);
3329                         SwitchClocks(forwardMostMove-1); // [HGM] race
3330                         DisplayBothClocks();
3331                     }
3332                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3333                     ics_user_moved = 0;
3334                     continue;
3335                 }
3336             }
3337
3338             if (looking_at(buf, &i, "still have time") ||
3339                 looking_at(buf, &i, "not out of time") ||
3340                 looking_at(buf, &i, "either player is out of time") ||
3341                 looking_at(buf, &i, "has timeseal; checking")) {
3342                 /* We must have called his flag a little too soon */
3343                 whiteFlag = blackFlag = FALSE;
3344                 continue;
3345             }
3346
3347             if (looking_at(buf, &i, "added * seconds to") ||
3348                 looking_at(buf, &i, "seconds were added to")) {
3349                 /* Update the clocks */
3350                 SendToICS(ics_prefix);
3351                 SendToICS("refresh\n");
3352                 continue;
3353             }
3354
3355             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3356                 ics_clock_paused = TRUE;
3357                 StopClocks();
3358                 continue;
3359             }
3360
3361             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3362                 ics_clock_paused = FALSE;
3363                 StartClocks();
3364                 continue;
3365             }
3366
3367             /* Grab player ratings from the Creating: message.
3368                Note we have to check for the special case when
3369                the ICS inserts things like [white] or [black]. */
3370             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3371                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3372                 /* star_matches:
3373                    0    player 1 name (not necessarily white)
3374                    1    player 1 rating
3375                    2    empty, white, or black (IGNORED)
3376                    3    player 2 name (not necessarily black)
3377                    4    player 2 rating
3378                    
3379                    The names/ratings are sorted out when the game
3380                    actually starts (below).
3381                 */
3382                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3383                 player1Rating = string_to_rating(star_match[1]);
3384                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3385                 player2Rating = string_to_rating(star_match[4]);
3386
3387                 if (appData.debugMode)
3388                   fprintf(debugFP, 
3389                           "Ratings from 'Creating:' %s %d, %s %d\n",
3390                           player1Name, player1Rating,
3391                           player2Name, player2Rating);
3392
3393                 continue;
3394             }
3395             
3396             /* Improved generic start/end-of-game messages */
3397             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3398                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3399                 /* If tkind == 0: */
3400                 /* star_match[0] is the game number */
3401                 /*           [1] is the white player's name */
3402                 /*           [2] is the black player's name */
3403                 /* For end-of-game: */
3404                 /*           [3] is the reason for the game end */
3405                 /*           [4] is a PGN end game-token, preceded by " " */
3406                 /* For start-of-game: */
3407                 /*           [3] begins with "Creating" or "Continuing" */
3408                 /*           [4] is " *" or empty (don't care). */
3409                 int gamenum = atoi(star_match[0]);
3410                 char *whitename, *blackname, *why, *endtoken;
3411                 ChessMove endtype = (ChessMove) 0;
3412
3413                 if (tkind == 0) {
3414                   whitename = star_match[1];
3415                   blackname = star_match[2];
3416                   why = star_match[3];
3417                   endtoken = star_match[4];
3418                 } else {
3419                   whitename = star_match[1];
3420                   blackname = star_match[3];
3421                   why = star_match[5];
3422                   endtoken = star_match[6];
3423                 }
3424
3425                 /* Game start messages */
3426                 if (strncmp(why, "Creating ", 9) == 0 ||
3427                     strncmp(why, "Continuing ", 11) == 0) {
3428                     gs_gamenum = gamenum;
3429                     strcpy(gs_kind, strchr(why, ' ') + 1);
3430                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3431 #if ZIPPY
3432                     if (appData.zippyPlay) {
3433                         ZippyGameStart(whitename, blackname);
3434                     }
3435 #endif /*ZIPPY*/
3436                     continue;
3437                 }
3438
3439                 /* Game end messages */
3440                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3441                     ics_gamenum != gamenum) {
3442                     continue;
3443                 }
3444                 while (endtoken[0] == ' ') endtoken++;
3445                 switch (endtoken[0]) {
3446                   case '*':
3447                   default:
3448                     endtype = GameUnfinished;
3449                     break;
3450                   case '0':
3451                     endtype = BlackWins;
3452                     break;
3453                   case '1':
3454                     if (endtoken[1] == '/')
3455                       endtype = GameIsDrawn;
3456                     else
3457                       endtype = WhiteWins;
3458                     break;
3459                 }
3460                 GameEnds(endtype, why, GE_ICS);
3461 #if ZIPPY
3462                 if (appData.zippyPlay && first.initDone) {
3463                     ZippyGameEnd(endtype, why);
3464                     if (first.pr == NULL) {
3465                       /* Start the next process early so that we'll
3466                          be ready for the next challenge */
3467                       StartChessProgram(&first);
3468                     }
3469                     /* Send "new" early, in case this command takes
3470                        a long time to finish, so that we'll be ready
3471                        for the next challenge. */
3472                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3473                     Reset(TRUE, TRUE);
3474                 }
3475 #endif /*ZIPPY*/
3476                 continue;
3477             }
3478
3479             if (looking_at(buf, &i, "Removing game * from observation") ||
3480                 looking_at(buf, &i, "no longer observing game *") ||
3481                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3482                 if (gameMode == IcsObserving &&
3483                     atoi(star_match[0]) == ics_gamenum)
3484                   {
3485                       /* icsEngineAnalyze */
3486                       if (appData.icsEngineAnalyze) {
3487                             ExitAnalyzeMode();
3488                             ModeHighlight();
3489                       }
3490                       StopClocks();
3491                       gameMode = IcsIdle;
3492                       ics_gamenum = -1;
3493                       ics_user_moved = FALSE;
3494                   }
3495                 continue;
3496             }
3497
3498             if (looking_at(buf, &i, "no longer examining game *")) {
3499                 if (gameMode == IcsExamining &&
3500                     atoi(star_match[0]) == ics_gamenum)
3501                   {
3502                       gameMode = IcsIdle;
3503                       ics_gamenum = -1;
3504                       ics_user_moved = FALSE;
3505                   }
3506                 continue;
3507             }
3508
3509             /* Advance leftover_start past any newlines we find,
3510                so only partial lines can get reparsed */
3511             if (looking_at(buf, &i, "\n")) {
3512                 prevColor = curColor;
3513                 if (curColor != ColorNormal) {
3514                     if (oldi > next_out) {
3515                         SendToPlayer(&buf[next_out], oldi - next_out);
3516                         next_out = oldi;
3517                     }
3518                     Colorize(ColorNormal, FALSE);
3519                     curColor = ColorNormal;
3520                 }
3521                 if (started == STARTED_BOARD) {
3522                     started = STARTED_NONE;
3523                     parse[parse_pos] = NULLCHAR;
3524                     ParseBoard12(parse);
3525                     ics_user_moved = 0;
3526
3527                     /* Send premove here */
3528                     if (appData.premove) {
3529                       char str[MSG_SIZ];
3530                       if (currentMove == 0 &&
3531                           gameMode == IcsPlayingWhite &&
3532                           appData.premoveWhite) {
3533                         sprintf(str, "%s\n", appData.premoveWhiteText);
3534                         if (appData.debugMode)
3535                           fprintf(debugFP, "Sending premove:\n");
3536                         SendToICS(str);
3537                       } else if (currentMove == 1 &&
3538                                  gameMode == IcsPlayingBlack &&
3539                                  appData.premoveBlack) {
3540                         sprintf(str, "%s\n", appData.premoveBlackText);
3541                         if (appData.debugMode)
3542                           fprintf(debugFP, "Sending premove:\n");
3543                         SendToICS(str);
3544                       } else if (gotPremove) {
3545                         gotPremove = 0;
3546                         ClearPremoveHighlights();
3547                         if (appData.debugMode)
3548                           fprintf(debugFP, "Sending premove:\n");
3549                           UserMoveEvent(premoveFromX, premoveFromY, 
3550                                         premoveToX, premoveToY, 
3551                                         premovePromoChar);
3552                       }
3553                     }
3554
3555                     /* Usually suppress following prompt */
3556                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3557                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3558                         if (looking_at(buf, &i, "*% ")) {
3559                             savingComment = FALSE;
3560                             suppressKibitz = 0;
3561                         }
3562                     }
3563                     next_out = i;
3564                 } else if (started == STARTED_HOLDINGS) {
3565                     int gamenum;
3566                     char new_piece[MSG_SIZ];
3567                     started = STARTED_NONE;
3568                     parse[parse_pos] = NULLCHAR;
3569                     if (appData.debugMode)
3570                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3571                                                         parse, currentMove);
3572                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3573                         gamenum == ics_gamenum) {
3574                         if (gameInfo.variant == VariantNormal) {
3575                           /* [HGM] We seem to switch variant during a game!
3576                            * Presumably no holdings were displayed, so we have
3577                            * to move the position two files to the right to
3578                            * create room for them!
3579                            */
3580                           VariantClass newVariant;
3581                           switch(gameInfo.boardWidth) { // base guess on board width
3582                                 case 9:  newVariant = VariantShogi; break;
3583                                 case 10: newVariant = VariantGreat; break;
3584                                 default: newVariant = VariantCrazyhouse; break;
3585                           }
3586                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3587                           /* Get a move list just to see the header, which
3588                              will tell us whether this is really bug or zh */
3589                           if (ics_getting_history == H_FALSE) {
3590                             ics_getting_history = H_REQUESTED;
3591                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3592                             SendToICS(str);
3593                           }
3594                         }
3595                         new_piece[0] = NULLCHAR;
3596                         sscanf(parse, "game %d white [%s black [%s <- %s",
3597                                &gamenum, white_holding, black_holding,
3598                                new_piece);
3599                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3600                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3601                         /* [HGM] copy holdings to board holdings area */
3602                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3603                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3604                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3605 #if ZIPPY
3606                         if (appData.zippyPlay && first.initDone) {
3607                             ZippyHoldings(white_holding, black_holding,
3608                                           new_piece);
3609                         }
3610 #endif /*ZIPPY*/
3611                         if (tinyLayout || smallLayout) {
3612                             char wh[16], bh[16];
3613                             PackHolding(wh, white_holding);
3614                             PackHolding(bh, black_holding);
3615                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3616                                     gameInfo.white, gameInfo.black);
3617                         } else {
3618                             sprintf(str, "%s [%s] vs. %s [%s]",
3619                                     gameInfo.white, white_holding,
3620                                     gameInfo.black, black_holding);
3621                         }
3622
3623                         DrawPosition(FALSE, boards[currentMove]);
3624                         DisplayTitle(str);
3625                     }
3626                     /* Suppress following prompt */
3627                     if (looking_at(buf, &i, "*% ")) {
3628                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3629                         savingComment = FALSE;
3630                         suppressKibitz = 0;
3631                     }
3632                     next_out = i;
3633                 }
3634                 continue;
3635             }
3636
3637             i++;                /* skip unparsed character and loop back */
3638         }
3639         
3640         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3641 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3642 //          SendToPlayer(&buf[next_out], i - next_out);
3643             started != STARTED_HOLDINGS && leftover_start > next_out) {
3644             SendToPlayer(&buf[next_out], leftover_start - next_out);
3645             next_out = i;
3646         }
3647         
3648         leftover_len = buf_len - leftover_start;
3649         /* if buffer ends with something we couldn't parse,
3650            reparse it after appending the next read */
3651         
3652     } else if (count == 0) {
3653         RemoveInputSource(isr);
3654         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3655     } else {
3656         DisplayFatalError(_("Error reading from ICS"), error, 1);
3657     }
3658 }
3659
3660
3661 /* Board style 12 looks like this:
3662    
3663    <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
3664    
3665  * The "<12> " is stripped before it gets to this routine.  The two
3666  * trailing 0's (flip state and clock ticking) are later addition, and
3667  * some chess servers may not have them, or may have only the first.
3668  * Additional trailing fields may be added in the future.  
3669  */
3670
3671 #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"
3672
3673 #define RELATION_OBSERVING_PLAYED    0
3674 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3675 #define RELATION_PLAYING_MYMOVE      1
3676 #define RELATION_PLAYING_NOTMYMOVE  -1
3677 #define RELATION_EXAMINING           2
3678 #define RELATION_ISOLATED_BOARD     -3
3679 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3680
3681 void
3682 ParseBoard12(string)
3683      char *string;
3684
3685     GameMode newGameMode;
3686     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3687     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3688     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3689     char to_play, board_chars[200];
3690     char move_str[500], str[500], elapsed_time[500];
3691     char black[32], white[32];
3692     Board board;
3693     int prevMove = currentMove;
3694     int ticking = 2;
3695     ChessMove moveType;
3696     int fromX, fromY, toX, toY;
3697     char promoChar;
3698     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3699     char *bookHit = NULL; // [HGM] book
3700     Boolean weird = FALSE, reqFlag = FALSE;
3701
3702     fromX = fromY = toX = toY = -1;
3703     
3704     newGame = FALSE;
3705
3706     if (appData.debugMode)
3707       fprintf(debugFP, _("Parsing board: %s\n"), string);
3708
3709     move_str[0] = NULLCHAR;
3710     elapsed_time[0] = NULLCHAR;
3711     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3712         int  i = 0, j;
3713         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3714             if(string[i] == ' ') { ranks++; files = 0; }
3715             else files++;
3716             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3717             i++;
3718         }
3719         for(j = 0; j <i; j++) board_chars[j] = string[j];
3720         board_chars[i] = '\0';
3721         string += i + 1;
3722     }
3723     n = sscanf(string, PATTERN, &to_play, &double_push,
3724                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3725                &gamenum, white, black, &relation, &basetime, &increment,
3726                &white_stren, &black_stren, &white_time, &black_time,
3727                &moveNum, str, elapsed_time, move_str, &ics_flip,
3728                &ticking);
3729
3730     if (n < 21) {
3731         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3732         DisplayError(str, 0);
3733         return;
3734     }
3735
3736     /* Convert the move number to internal form */
3737     moveNum = (moveNum - 1) * 2;
3738     if (to_play == 'B') moveNum++;
3739     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3740       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3741                         0, 1);
3742       return;
3743     }
3744     
3745     switch (relation) {
3746       case RELATION_OBSERVING_PLAYED:
3747       case RELATION_OBSERVING_STATIC:
3748         if (gamenum == -1) {
3749             /* Old ICC buglet */
3750             relation = RELATION_OBSERVING_STATIC;
3751         }
3752         newGameMode = IcsObserving;
3753         break;
3754       case RELATION_PLAYING_MYMOVE:
3755       case RELATION_PLAYING_NOTMYMOVE:
3756         newGameMode =
3757           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3758             IcsPlayingWhite : IcsPlayingBlack;
3759         break;
3760       case RELATION_EXAMINING:
3761         newGameMode = IcsExamining;
3762         break;
3763       case RELATION_ISOLATED_BOARD:
3764       default:
3765         /* Just display this board.  If user was doing something else,
3766            we will forget about it until the next board comes. */ 
3767         newGameMode = IcsIdle;
3768         break;
3769       case RELATION_STARTING_POSITION:
3770         newGameMode = gameMode;
3771         break;
3772     }
3773     
3774     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3775          && newGameMode == IcsObserving && appData.bgObserve) {
3776       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3777       char buf[MSG_SIZ];
3778       for (k = 0; k < ranks; k++) {
3779         for (j = 0; j < files; j++)
3780           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3781         if(gameInfo.holdingsWidth > 1) {
3782              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3783              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3784         }
3785       }
3786       CopyBoard(partnerBoard, board);
3787       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3788       sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3789                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3790       DisplayMessage(buf, "");
3791       return;
3792     }
3793
3794     /* Modify behavior for initial board display on move listing
3795        of wild games.
3796        */
3797     switch (ics_getting_history) {
3798       case H_FALSE:
3799       case H_REQUESTED:
3800         break;
3801       case H_GOT_REQ_HEADER:
3802       case H_GOT_UNREQ_HEADER:
3803         /* This is the initial position of the current game */
3804         gamenum = ics_gamenum;
3805         moveNum = 0;            /* old ICS bug workaround */
3806         if (to_play == 'B') {
3807           startedFromSetupPosition = TRUE;
3808           blackPlaysFirst = TRUE;
3809           moveNum = 1;
3810           if (forwardMostMove == 0) forwardMostMove = 1;
3811           if (backwardMostMove == 0) backwardMostMove = 1;
3812           if (currentMove == 0) currentMove = 1;
3813         }
3814         newGameMode = gameMode;
3815         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3816         break;
3817       case H_GOT_UNWANTED_HEADER:
3818         /* This is an initial board that we don't want */
3819         return;
3820       case H_GETTING_MOVES:
3821         /* Should not happen */
3822         DisplayError(_("Error gathering move list: extra board"), 0);
3823         ics_getting_history = H_FALSE;
3824         return;
3825     }
3826
3827    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3828                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3829      /* [HGM] We seem to have switched variant unexpectedly
3830       * Try to guess new variant from board size
3831       */
3832           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3833           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3834           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3835           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3836           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3837           if(!weird) newVariant = VariantNormal;
3838           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3839           /* Get a move list just to see the header, which
3840              will tell us whether this is really bug or zh */
3841           if (ics_getting_history == H_FALSE) {
3842             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3843             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3844             SendToICS(str);
3845           }
3846     }
3847     
3848     /* Take action if this is the first board of a new game, or of a
3849        different game than is currently being displayed.  */
3850     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3851         relation == RELATION_ISOLATED_BOARD) {
3852         
3853         /* Forget the old game and get the history (if any) of the new one */
3854         if (gameMode != BeginningOfGame) {
3855           Reset(TRUE, TRUE);
3856         }
3857         newGame = TRUE;
3858         if (appData.autoRaiseBoard) BoardToTop();
3859         prevMove = -3;
3860         if (gamenum == -1) {
3861             newGameMode = IcsIdle;
3862         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3863                    appData.getMoveList && !reqFlag) {
3864             /* Need to get game history */
3865             ics_getting_history = H_REQUESTED;
3866             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3867             SendToICS(str);
3868         }
3869         
3870         /* Initially flip the board to have black on the bottom if playing
3871            black or if the ICS flip flag is set, but let the user change
3872            it with the Flip View button. */
3873         flipView = appData.autoFlipView ? 
3874           (newGameMode == IcsPlayingBlack) || ics_flip :
3875           appData.flipView;
3876         
3877         /* Done with values from previous mode; copy in new ones */
3878         gameMode = newGameMode;
3879         ModeHighlight();
3880         ics_gamenum = gamenum;
3881         if (gamenum == gs_gamenum) {
3882             int klen = strlen(gs_kind);
3883             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3884             sprintf(str, "ICS %s", gs_kind);
3885             gameInfo.event = StrSave(str);
3886         } else {
3887             gameInfo.event = StrSave("ICS game");
3888         }
3889         gameInfo.site = StrSave(appData.icsHost);
3890         gameInfo.date = PGNDate();
3891         gameInfo.round = StrSave("-");
3892         gameInfo.white = StrSave(white);
3893         gameInfo.black = StrSave(black);
3894         timeControl = basetime * 60 * 1000;
3895         timeControl_2 = 0;
3896         timeIncrement = increment * 1000;
3897         movesPerSession = 0;
3898         gameInfo.timeControl = TimeControlTagValue();
3899         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3900   if (appData.debugMode) {
3901     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3902     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3903     setbuf(debugFP, NULL);
3904   }
3905
3906         gameInfo.outOfBook = NULL;
3907         
3908         /* Do we have the ratings? */
3909         if (strcmp(player1Name, white) == 0 &&
3910             strcmp(player2Name, black) == 0) {
3911             if (appData.debugMode)
3912               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3913                       player1Rating, player2Rating);
3914             gameInfo.whiteRating = player1Rating;
3915             gameInfo.blackRating = player2Rating;
3916         } else if (strcmp(player2Name, white) == 0 &&
3917                    strcmp(player1Name, black) == 0) {
3918             if (appData.debugMode)
3919               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3920                       player2Rating, player1Rating);
3921             gameInfo.whiteRating = player2Rating;
3922             gameInfo.blackRating = player1Rating;
3923         }
3924         player1Name[0] = player2Name[0] = NULLCHAR;
3925
3926         /* Silence shouts if requested */
3927         if (appData.quietPlay &&
3928             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3929             SendToICS(ics_prefix);
3930             SendToICS("set shout 0\n");
3931         }
3932     }
3933     
3934     /* Deal with midgame name changes */
3935     if (!newGame) {
3936         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3937             if (gameInfo.white) free(gameInfo.white);
3938             gameInfo.white = StrSave(white);
3939         }
3940         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3941             if (gameInfo.black) free(gameInfo.black);
3942             gameInfo.black = StrSave(black);
3943         }
3944     }
3945     
3946     /* Throw away game result if anything actually changes in examine mode */
3947     if (gameMode == IcsExamining && !newGame) {
3948         gameInfo.result = GameUnfinished;
3949         if (gameInfo.resultDetails != NULL) {
3950             free(gameInfo.resultDetails);
3951             gameInfo.resultDetails = NULL;
3952         }
3953     }
3954     
3955     /* In pausing && IcsExamining mode, we ignore boards coming
3956        in if they are in a different variation than we are. */
3957     if (pauseExamInvalid) return;
3958     if (pausing && gameMode == IcsExamining) {
3959         if (moveNum <= pauseExamForwardMostMove) {
3960             pauseExamInvalid = TRUE;
3961             forwardMostMove = pauseExamForwardMostMove;
3962             return;
3963         }
3964     }
3965     
3966   if (appData.debugMode) {
3967     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3968   }
3969     /* Parse the board */
3970     for (k = 0; k < ranks; k++) {
3971       for (j = 0; j < files; j++)
3972         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3973       if(gameInfo.holdingsWidth > 1) {
3974            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3975            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3976       }
3977     }
3978     CopyBoard(boards[moveNum], board);
3979     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3980     if (moveNum == 0) {
3981         startedFromSetupPosition =
3982           !CompareBoards(board, initialPosition);
3983         if(startedFromSetupPosition)
3984             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3985     }
3986
3987     /* [HGM] Set castling rights. Take the outermost Rooks,
3988        to make it also work for FRC opening positions. Note that board12
3989        is really defective for later FRC positions, as it has no way to
3990        indicate which Rook can castle if they are on the same side of King.
3991        For the initial position we grant rights to the outermost Rooks,
3992        and remember thos rights, and we then copy them on positions
3993        later in an FRC game. This means WB might not recognize castlings with
3994        Rooks that have moved back to their original position as illegal,
3995        but in ICS mode that is not its job anyway.
3996     */
3997     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3998     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3999
4000         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4001             if(board[0][i] == WhiteRook) j = i;
4002         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4003         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4004             if(board[0][i] == WhiteRook) j = i;
4005         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4006         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4007             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4008         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4009         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4010             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4011         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4012
4013         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4014         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4015             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4016         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4017             if(board[BOARD_HEIGHT-1][k] == bKing)
4018                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4019         if(gameInfo.variant == VariantTwoKings) {
4020             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4021             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4022             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4023         }
4024     } else { int r;
4025         r = boards[moveNum][CASTLING][0] = initialRights[0];
4026         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4027         r = boards[moveNum][CASTLING][1] = initialRights[1];
4028         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4029         r = boards[moveNum][CASTLING][3] = initialRights[3];
4030         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4031         r = boards[moveNum][CASTLING][4] = initialRights[4];
4032         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4033         /* wildcastle kludge: always assume King has rights */
4034         r = boards[moveNum][CASTLING][2] = initialRights[2];
4035         r = boards[moveNum][CASTLING][5] = initialRights[5];
4036     }
4037     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4038     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4039
4040     
4041     if (ics_getting_history == H_GOT_REQ_HEADER ||
4042         ics_getting_history == H_GOT_UNREQ_HEADER) {
4043         /* This was an initial position from a move list, not
4044            the current position */
4045         return;
4046     }
4047     
4048     /* Update currentMove and known move number limits */
4049     newMove = newGame || moveNum > forwardMostMove;
4050
4051     if (newGame) {
4052         forwardMostMove = backwardMostMove = currentMove = moveNum;
4053         if (gameMode == IcsExamining && moveNum == 0) {
4054           /* Workaround for ICS limitation: we are not told the wild
4055              type when starting to examine a game.  But if we ask for
4056              the move list, the move list header will tell us */
4057             ics_getting_history = H_REQUESTED;
4058             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4059             SendToICS(str);
4060         }
4061     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4062                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4063 #if ZIPPY
4064         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4065         /* [HGM] applied this also to an engine that is silently watching        */
4066         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4067             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4068             gameInfo.variant == currentlyInitializedVariant) {
4069           takeback = forwardMostMove - moveNum;
4070           for (i = 0; i < takeback; i++) {
4071             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4072             SendToProgram("undo\n", &first);
4073           }
4074         }
4075 #endif
4076
4077         forwardMostMove = moveNum;
4078         if (!pausing || currentMove > forwardMostMove)
4079           currentMove = forwardMostMove;
4080     } else {
4081         /* New part of history that is not contiguous with old part */ 
4082         if (pausing && gameMode == IcsExamining) {
4083             pauseExamInvalid = TRUE;
4084             forwardMostMove = pauseExamForwardMostMove;
4085             return;
4086         }
4087         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4088 #if ZIPPY
4089             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4090                 // [HGM] when we will receive the move list we now request, it will be
4091                 // fed to the engine from the first move on. So if the engine is not
4092                 // in the initial position now, bring it there.
4093                 InitChessProgram(&first, 0);
4094             }
4095 #endif
4096             ics_getting_history = H_REQUESTED;
4097             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4098             SendToICS(str);
4099         }
4100         forwardMostMove = backwardMostMove = currentMove = moveNum;
4101     }
4102     
4103     /* Update the clocks */
4104     if (strchr(elapsed_time, '.')) {
4105       /* Time is in ms */
4106       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4107       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4108     } else {
4109       /* Time is in seconds */
4110       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4111       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4112     }
4113       
4114
4115 #if ZIPPY
4116     if (appData.zippyPlay && newGame &&
4117         gameMode != IcsObserving && gameMode != IcsIdle &&
4118         gameMode != IcsExamining)
4119       ZippyFirstBoard(moveNum, basetime, increment);
4120 #endif
4121     
4122     /* Put the move on the move list, first converting
4123        to canonical algebraic form. */
4124     if (moveNum > 0) {
4125   if (appData.debugMode) {
4126     if (appData.debugMode) { int f = forwardMostMove;
4127         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4128                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4129                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4130     }
4131     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4132     fprintf(debugFP, "moveNum = %d\n", moveNum);
4133     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4134     setbuf(debugFP, NULL);
4135   }
4136         if (moveNum <= backwardMostMove) {
4137             /* We don't know what the board looked like before
4138                this move.  Punt. */
4139             strcpy(parseList[moveNum - 1], move_str);
4140             strcat(parseList[moveNum - 1], " ");
4141             strcat(parseList[moveNum - 1], elapsed_time);
4142             moveList[moveNum - 1][0] = NULLCHAR;
4143         } else if (strcmp(move_str, "none") == 0) {
4144             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4145             /* Again, we don't know what the board looked like;
4146                this is really the start of the game. */
4147             parseList[moveNum - 1][0] = NULLCHAR;
4148             moveList[moveNum - 1][0] = NULLCHAR;
4149             backwardMostMove = moveNum;
4150             startedFromSetupPosition = TRUE;
4151             fromX = fromY = toX = toY = -1;
4152         } else {
4153           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4154           //                 So we parse the long-algebraic move string in stead of the SAN move
4155           int valid; char buf[MSG_SIZ], *prom;
4156
4157           // str looks something like "Q/a1-a2"; kill the slash
4158           if(str[1] == '/') 
4159                 sprintf(buf, "%c%s", str[0], str+2);
4160           else  strcpy(buf, str); // might be castling
4161           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4162                 strcat(buf, prom); // long move lacks promo specification!
4163           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4164                 if(appData.debugMode) 
4165                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4166                 strcpy(move_str, buf);
4167           }
4168           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4169                                 &fromX, &fromY, &toX, &toY, &promoChar)
4170                || ParseOneMove(buf, moveNum - 1, &moveType,
4171                                 &fromX, &fromY, &toX, &toY, &promoChar);
4172           // end of long SAN patch
4173           if (valid) {
4174             (void) CoordsToAlgebraic(boards[moveNum - 1],
4175                                      PosFlags(moveNum - 1),
4176                                      fromY, fromX, toY, toX, promoChar,
4177                                      parseList[moveNum-1]);
4178             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4179               case MT_NONE:
4180               case MT_STALEMATE:
4181               default:
4182                 break;
4183               case MT_CHECK:
4184                 if(gameInfo.variant != VariantShogi)
4185                     strcat(parseList[moveNum - 1], "+");
4186                 break;
4187               case MT_CHECKMATE:
4188               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4189                 strcat(parseList[moveNum - 1], "#");
4190                 break;
4191             }
4192             strcat(parseList[moveNum - 1], " ");
4193             strcat(parseList[moveNum - 1], elapsed_time);
4194             /* currentMoveString is set as a side-effect of ParseOneMove */
4195             strcpy(moveList[moveNum - 1], currentMoveString);
4196             strcat(moveList[moveNum - 1], "\n");
4197           } else {
4198             /* Move from ICS was illegal!?  Punt. */
4199   if (appData.debugMode) {
4200     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4201     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4202   }
4203             strcpy(parseList[moveNum - 1], move_str);
4204             strcat(parseList[moveNum - 1], " ");
4205             strcat(parseList[moveNum - 1], elapsed_time);
4206             moveList[moveNum - 1][0] = NULLCHAR;
4207             fromX = fromY = toX = toY = -1;
4208           }
4209         }
4210   if (appData.debugMode) {
4211     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4212     setbuf(debugFP, NULL);
4213   }
4214
4215 #if ZIPPY
4216         /* Send move to chess program (BEFORE animating it). */
4217         if (appData.zippyPlay && !newGame && newMove && 
4218            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4219
4220             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4221                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4222                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4223                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4224                             move_str);
4225                     DisplayError(str, 0);
4226                 } else {
4227                     if (first.sendTime) {
4228                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4229                     }
4230                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4231                     if (firstMove && !bookHit) {
4232                         firstMove = FALSE;
4233                         if (first.useColors) {
4234                           SendToProgram(gameMode == IcsPlayingWhite ?
4235                                         "white\ngo\n" :
4236                                         "black\ngo\n", &first);
4237                         } else {
4238                           SendToProgram("go\n", &first);
4239                         }
4240                         first.maybeThinking = TRUE;
4241                     }
4242                 }
4243             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4244               if (moveList[moveNum - 1][0] == NULLCHAR) {
4245                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4246                 DisplayError(str, 0);
4247               } else {
4248                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4249                 SendMoveToProgram(moveNum - 1, &first);
4250               }
4251             }
4252         }
4253 #endif
4254     }
4255
4256     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4257         /* If move comes from a remote source, animate it.  If it
4258            isn't remote, it will have already been animated. */
4259         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4260             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4261         }
4262         if (!pausing && appData.highlightLastMove) {
4263             SetHighlights(fromX, fromY, toX, toY);
4264         }
4265     }
4266     
4267     /* Start the clocks */
4268     whiteFlag = blackFlag = FALSE;
4269     appData.clockMode = !(basetime == 0 && increment == 0);
4270     if (ticking == 0) {
4271       ics_clock_paused = TRUE;
4272       StopClocks();
4273     } else if (ticking == 1) {
4274       ics_clock_paused = FALSE;
4275     }
4276     if (gameMode == IcsIdle ||
4277         relation == RELATION_OBSERVING_STATIC ||
4278         relation == RELATION_EXAMINING ||
4279         ics_clock_paused)
4280       DisplayBothClocks();
4281     else
4282       StartClocks();
4283     
4284     /* Display opponents and material strengths */
4285     if (gameInfo.variant != VariantBughouse &&
4286         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4287         if (tinyLayout || smallLayout) {
4288             if(gameInfo.variant == VariantNormal)
4289                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4290                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4291                     basetime, increment);
4292             else
4293                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4294                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4295                     basetime, increment, (int) gameInfo.variant);
4296         } else {
4297             if(gameInfo.variant == VariantNormal)
4298                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4299                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4300                     basetime, increment);
4301             else
4302                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4303                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4304                     basetime, increment, VariantName(gameInfo.variant));
4305         }
4306         DisplayTitle(str);
4307   if (appData.debugMode) {
4308     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4309   }
4310     }
4311
4312
4313     /* Display the board */
4314     if (!pausing && !appData.noGUI) {
4315       
4316       if (appData.premove)
4317           if (!gotPremove || 
4318              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4319              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4320               ClearPremoveHighlights();
4321
4322       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4323       DrawPosition(j, boards[currentMove]);
4324
4325       DisplayMove(moveNum - 1);
4326       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4327             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4328               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4329         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4330       }
4331     }
4332
4333     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4334 #if ZIPPY
4335     if(bookHit) { // [HGM] book: simulate book reply
4336         static char bookMove[MSG_SIZ]; // a bit generous?
4337
4338         programStats.nodes = programStats.depth = programStats.time = 
4339         programStats.score = programStats.got_only_move = 0;
4340         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4341
4342         strcpy(bookMove, "move ");
4343         strcat(bookMove, bookHit);
4344         HandleMachineMove(bookMove, &first);
4345     }
4346 #endif
4347 }
4348
4349 void
4350 GetMoveListEvent()
4351 {
4352     char buf[MSG_SIZ];
4353     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4354         ics_getting_history = H_REQUESTED;
4355         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4356         SendToICS(buf);
4357     }
4358 }
4359
4360 void
4361 AnalysisPeriodicEvent(force)
4362      int force;
4363 {
4364     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4365          && !force) || !appData.periodicUpdates)
4366       return;
4367
4368     /* Send . command to Crafty to collect stats */
4369     SendToProgram(".\n", &first);
4370
4371     /* Don't send another until we get a response (this makes
4372        us stop sending to old Crafty's which don't understand
4373        the "." command (sending illegal cmds resets node count & time,
4374        which looks bad)) */
4375     programStats.ok_to_send = 0;
4376 }
4377
4378 void ics_update_width(new_width)
4379         int new_width;
4380 {
4381         ics_printf("set width %d\n", new_width);
4382 }
4383
4384 void
4385 SendMoveToProgram(moveNum, cps)
4386      int moveNum;
4387      ChessProgramState *cps;
4388 {
4389     char buf[MSG_SIZ];
4390
4391     if (cps->useUsermove) {
4392       SendToProgram("usermove ", cps);
4393     }
4394     if (cps->useSAN) {
4395       char *space;
4396       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4397         int len = space - parseList[moveNum];
4398         memcpy(buf, parseList[moveNum], len);
4399         buf[len++] = '\n';
4400         buf[len] = NULLCHAR;
4401       } else {
4402         sprintf(buf, "%s\n", parseList[moveNum]);
4403       }
4404       SendToProgram(buf, cps);
4405     } else {
4406       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4407         AlphaRank(moveList[moveNum], 4);
4408         SendToProgram(moveList[moveNum], cps);
4409         AlphaRank(moveList[moveNum], 4); // and back
4410       } else
4411       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4412        * the engine. It would be nice to have a better way to identify castle 
4413        * moves here. */
4414       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4415                                                                          && cps->useOOCastle) {
4416         int fromX = moveList[moveNum][0] - AAA; 
4417         int fromY = moveList[moveNum][1] - ONE;
4418         int toX = moveList[moveNum][2] - AAA; 
4419         int toY = moveList[moveNum][3] - ONE;
4420         if((boards[moveNum][fromY][fromX] == WhiteKing 
4421             && boards[moveNum][toY][toX] == WhiteRook)
4422            || (boards[moveNum][fromY][fromX] == BlackKing 
4423                && boards[moveNum][toY][toX] == BlackRook)) {
4424           if(toX > fromX) SendToProgram("O-O\n", cps);
4425           else SendToProgram("O-O-O\n", cps);
4426         }
4427         else SendToProgram(moveList[moveNum], cps);
4428       }
4429       else SendToProgram(moveList[moveNum], cps);
4430       /* End of additions by Tord */
4431     }
4432
4433     /* [HGM] setting up the opening has brought engine in force mode! */
4434     /*       Send 'go' if we are in a mode where machine should play. */
4435     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4436         (gameMode == TwoMachinesPlay   ||
4437 #ifdef ZIPPY
4438          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4439 #endif
4440          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4441         SendToProgram("go\n", cps);
4442   if (appData.debugMode) {
4443     fprintf(debugFP, "(extra)\n");
4444   }
4445     }
4446     setboardSpoiledMachineBlack = 0;
4447 }
4448
4449 void
4450 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4451      ChessMove moveType;
4452      int fromX, fromY, toX, toY;
4453 {
4454     char user_move[MSG_SIZ];
4455
4456     switch (moveType) {
4457       default:
4458         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4459                 (int)moveType, fromX, fromY, toX, toY);
4460         DisplayError(user_move + strlen("say "), 0);
4461         break;
4462       case WhiteKingSideCastle:
4463       case BlackKingSideCastle:
4464       case WhiteQueenSideCastleWild:
4465       case BlackQueenSideCastleWild:
4466       /* PUSH Fabien */
4467       case WhiteHSideCastleFR:
4468       case BlackHSideCastleFR:
4469       /* POP Fabien */
4470         sprintf(user_move, "o-o\n");
4471         break;
4472       case WhiteQueenSideCastle:
4473       case BlackQueenSideCastle:
4474       case WhiteKingSideCastleWild:
4475       case BlackKingSideCastleWild:
4476       /* PUSH Fabien */
4477       case WhiteASideCastleFR:
4478       case BlackASideCastleFR:
4479       /* POP Fabien */
4480         sprintf(user_move, "o-o-o\n");
4481         break;
4482       case WhitePromotionQueen:
4483       case BlackPromotionQueen:
4484       case WhitePromotionRook:
4485       case BlackPromotionRook:
4486       case WhitePromotionBishop:
4487       case BlackPromotionBishop:
4488       case WhitePromotionKnight:
4489       case BlackPromotionKnight:
4490       case WhitePromotionKing:
4491       case BlackPromotionKing:
4492       case WhitePromotionChancellor:
4493       case BlackPromotionChancellor:
4494       case WhitePromotionArchbishop:
4495       case BlackPromotionArchbishop:
4496         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4497             sprintf(user_move, "%c%c%c%c=%c\n",
4498                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4499                 PieceToChar(WhiteFerz));
4500         else if(gameInfo.variant == VariantGreat)
4501             sprintf(user_move, "%c%c%c%c=%c\n",
4502                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4503                 PieceToChar(WhiteMan));
4504         else
4505             sprintf(user_move, "%c%c%c%c=%c\n",
4506                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4507                 PieceToChar(PromoPiece(moveType)));
4508         break;
4509       case WhiteDrop:
4510       case BlackDrop:
4511         sprintf(user_move, "%c@%c%c\n",
4512                 ToUpper(PieceToChar((ChessSquare) fromX)),
4513                 AAA + toX, ONE + toY);
4514         break;
4515       case NormalMove:
4516       case WhiteCapturesEnPassant:
4517       case BlackCapturesEnPassant:
4518       case IllegalMove:  /* could be a variant we don't quite understand */
4519         sprintf(user_move, "%c%c%c%c\n",
4520                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4521         break;
4522     }
4523     SendToICS(user_move);
4524     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4525         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4526 }
4527
4528 void
4529 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4530      int rf, ff, rt, ft;
4531      char promoChar;
4532      char move[7];
4533 {
4534     if (rf == DROP_RANK) {
4535         sprintf(move, "%c@%c%c\n",
4536                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4537     } else {
4538         if (promoChar == 'x' || promoChar == NULLCHAR) {
4539             sprintf(move, "%c%c%c%c\n",
4540                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4541         } else {
4542             sprintf(move, "%c%c%c%c%c\n",
4543                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4544         }
4545     }
4546 }
4547
4548 void
4549 ProcessICSInitScript(f)
4550      FILE *f;
4551 {
4552     char buf[MSG_SIZ];
4553
4554     while (fgets(buf, MSG_SIZ, f)) {
4555         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4556     }
4557
4558     fclose(f);
4559 }
4560
4561
4562 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4563 void
4564 AlphaRank(char *move, int n)
4565 {
4566 //    char *p = move, c; int x, y;
4567
4568     if (appData.debugMode) {
4569         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4570     }
4571
4572     if(move[1]=='*' && 
4573        move[2]>='0' && move[2]<='9' &&
4574        move[3]>='a' && move[3]<='x'    ) {
4575         move[1] = '@';
4576         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4577         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4578     } else
4579     if(move[0]>='0' && move[0]<='9' &&
4580        move[1]>='a' && move[1]<='x' &&
4581        move[2]>='0' && move[2]<='9' &&
4582        move[3]>='a' && move[3]<='x'    ) {
4583         /* input move, Shogi -> normal */
4584         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4585         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4586         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4587         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4588     } else
4589     if(move[1]=='@' &&
4590        move[3]>='0' && move[3]<='9' &&
4591        move[2]>='a' && move[2]<='x'    ) {
4592         move[1] = '*';
4593         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4594         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4595     } else
4596     if(
4597        move[0]>='a' && move[0]<='x' &&
4598        move[3]>='0' && move[3]<='9' &&
4599        move[2]>='a' && move[2]<='x'    ) {
4600          /* output move, normal -> Shogi */
4601         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4602         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4603         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4604         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4605         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4606     }
4607     if (appData.debugMode) {
4608         fprintf(debugFP, "   out = '%s'\n", move);
4609     }
4610 }
4611
4612 /* Parser for moves from gnuchess, ICS, or user typein box */
4613 Boolean
4614 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4615      char *move;
4616      int moveNum;
4617      ChessMove *moveType;
4618      int *fromX, *fromY, *toX, *toY;
4619      char *promoChar;
4620 {       
4621     if (appData.debugMode) {
4622         fprintf(debugFP, "move to parse: %s\n", move);
4623     }
4624     *moveType = yylexstr(moveNum, move);
4625
4626     switch (*moveType) {
4627       case WhitePromotionChancellor:
4628       case BlackPromotionChancellor:
4629       case WhitePromotionArchbishop:
4630       case BlackPromotionArchbishop:
4631       case WhitePromotionQueen:
4632       case BlackPromotionQueen:
4633       case WhitePromotionRook:
4634       case BlackPromotionRook:
4635       case WhitePromotionBishop:
4636       case BlackPromotionBishop:
4637       case WhitePromotionKnight:
4638       case BlackPromotionKnight:
4639       case WhitePromotionKing:
4640       case BlackPromotionKing:
4641       case NormalMove:
4642       case WhiteCapturesEnPassant:
4643       case BlackCapturesEnPassant:
4644       case WhiteKingSideCastle:
4645       case WhiteQueenSideCastle:
4646       case BlackKingSideCastle:
4647       case BlackQueenSideCastle:
4648       case WhiteKingSideCastleWild:
4649       case WhiteQueenSideCastleWild:
4650       case BlackKingSideCastleWild:
4651       case BlackQueenSideCastleWild:
4652       /* Code added by Tord: */
4653       case WhiteHSideCastleFR:
4654       case WhiteASideCastleFR:
4655       case BlackHSideCastleFR:
4656       case BlackASideCastleFR:
4657       /* End of code added by Tord */
4658       case IllegalMove:         /* bug or odd chess variant */
4659         *fromX = currentMoveString[0] - AAA;
4660         *fromY = currentMoveString[1] - ONE;
4661         *toX = currentMoveString[2] - AAA;
4662         *toY = currentMoveString[3] - ONE;
4663         *promoChar = currentMoveString[4];
4664         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4665             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4666     if (appData.debugMode) {
4667         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4668     }
4669             *fromX = *fromY = *toX = *toY = 0;
4670             return FALSE;
4671         }
4672         if (appData.testLegality) {
4673           return (*moveType != IllegalMove);
4674         } else {
4675           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4676                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4677         }
4678
4679       case WhiteDrop:
4680       case BlackDrop:
4681         *fromX = *moveType == WhiteDrop ?
4682           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4683           (int) CharToPiece(ToLower(currentMoveString[0]));
4684         *fromY = DROP_RANK;
4685         *toX = currentMoveString[2] - AAA;
4686         *toY = currentMoveString[3] - ONE;
4687         *promoChar = NULLCHAR;
4688         return TRUE;
4689
4690       case AmbiguousMove:
4691       case ImpossibleMove:
4692       case (ChessMove) 0:       /* end of file */
4693       case ElapsedTime:
4694       case Comment:
4695       case PGNTag:
4696       case NAG:
4697       case WhiteWins:
4698       case BlackWins:
4699       case GameIsDrawn:
4700       default:
4701     if (appData.debugMode) {
4702         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4703     }
4704         /* bug? */
4705         *fromX = *fromY = *toX = *toY = 0;
4706         *promoChar = NULLCHAR;
4707         return FALSE;
4708     }
4709 }
4710
4711
4712 void
4713 ParsePV(char *pv)
4714 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4715   int fromX, fromY, toX, toY; char promoChar;
4716   ChessMove moveType;
4717   Boolean valid;
4718   int nr = 0;
4719
4720   endPV = forwardMostMove;
4721   do {
4722     while(*pv == ' ') pv++;
4723     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4724     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4725 if(appData.debugMode){
4726 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4727 }
4728     if(!valid && nr == 0 &&
4729        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4730         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4731     }
4732     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4733     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4734     nr++;
4735     if(endPV+1 > framePtr) break; // no space, truncate
4736     if(!valid) break;
4737     endPV++;
4738     CopyBoard(boards[endPV], boards[endPV-1]);
4739     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4740     moveList[endPV-1][0] = fromX + AAA;
4741     moveList[endPV-1][1] = fromY + ONE;
4742     moveList[endPV-1][2] = toX + AAA;
4743     moveList[endPV-1][3] = toY + ONE;
4744     parseList[endPV-1][0] = NULLCHAR;
4745   } while(valid);
4746   currentMove = endPV;
4747   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4748   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4749                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4750   DrawPosition(TRUE, boards[currentMove]);
4751 }
4752
4753 static int lastX, lastY;
4754
4755 Boolean
4756 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4757 {
4758         int startPV;
4759
4760         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4761         lastX = x; lastY = y;
4762         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4763         startPV = index;
4764       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4765       index = startPV;
4766         while(buf[index] && buf[index] != '\n') index++;
4767         buf[index] = 0;
4768         ParsePV(buf+startPV);
4769         *start = startPV; *end = index-1;
4770         return TRUE;
4771 }
4772
4773 Boolean
4774 LoadPV(int x, int y)
4775 { // called on right mouse click to load PV
4776   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4777   lastX = x; lastY = y;
4778   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4779   return TRUE;
4780 }
4781
4782 void
4783 UnLoadPV()
4784 {
4785   if(endPV < 0) return;
4786   endPV = -1;
4787   currentMove = forwardMostMove;
4788   ClearPremoveHighlights();
4789   DrawPosition(TRUE, boards[currentMove]);
4790 }
4791
4792 void
4793 MovePV(int x, int y, int h)
4794 { // step through PV based on mouse coordinates (called on mouse move)
4795   int margin = h>>3, step = 0;
4796
4797   if(endPV < 0) return;
4798   // we must somehow check if right button is still down (might be released off board!)
4799   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4800   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4801   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4802   if(!step) return;
4803   lastX = x; lastY = y;
4804   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4805   currentMove += step;
4806   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4807   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4808                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4809   DrawPosition(FALSE, boards[currentMove]);
4810 }
4811
4812
4813 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4814 // All positions will have equal probability, but the current method will not provide a unique
4815 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4816 #define DARK 1
4817 #define LITE 2
4818 #define ANY 3
4819
4820 int squaresLeft[4];
4821 int piecesLeft[(int)BlackPawn];
4822 int seed, nrOfShuffles;
4823
4824 void GetPositionNumber()
4825 {       // sets global variable seed
4826         int i;
4827
4828         seed = appData.defaultFrcPosition;
4829         if(seed < 0) { // randomize based on time for negative FRC position numbers
4830                 for(i=0; i<50; i++) seed += random();
4831                 seed = random() ^ random() >> 8 ^ random() << 8;
4832                 if(seed<0) seed = -seed;
4833         }
4834 }
4835
4836 int put(Board board, int pieceType, int rank, int n, int shade)
4837 // put the piece on the (n-1)-th empty squares of the given shade
4838 {
4839         int i;
4840
4841         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4842                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4843                         board[rank][i] = (ChessSquare) pieceType;
4844                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4845                         squaresLeft[ANY]--;
4846                         piecesLeft[pieceType]--; 
4847                         return i;
4848                 }
4849         }
4850         return -1;
4851 }
4852
4853
4854 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4855 // calculate where the next piece goes, (any empty square), and put it there
4856 {
4857         int i;
4858
4859         i = seed % squaresLeft[shade];
4860         nrOfShuffles *= squaresLeft[shade];
4861         seed /= squaresLeft[shade];
4862         put(board, pieceType, rank, i, shade);
4863 }
4864
4865 void AddTwoPieces(Board board, int pieceType, int rank)
4866 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4867 {
4868         int i, n=squaresLeft[ANY], j=n-1, k;
4869
4870         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4871         i = seed % k;  // pick one
4872         nrOfShuffles *= k;
4873         seed /= k;
4874         while(i >= j) i -= j--;
4875         j = n - 1 - j; i += j;
4876         put(board, pieceType, rank, j, ANY);
4877         put(board, pieceType, rank, i, ANY);
4878 }
4879
4880 void SetUpShuffle(Board board, int number)
4881 {
4882         int i, p, first=1;
4883
4884         GetPositionNumber(); nrOfShuffles = 1;
4885
4886         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4887         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4888         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4889
4890         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4891
4892         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4893             p = (int) board[0][i];
4894             if(p < (int) BlackPawn) piecesLeft[p] ++;
4895             board[0][i] = EmptySquare;
4896         }
4897
4898         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4899             // shuffles restricted to allow normal castling put KRR first
4900             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4901                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4902             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4903                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4904             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4905                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4906             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4907                 put(board, WhiteRook, 0, 0, ANY);
4908             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4909         }
4910
4911         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4912             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4913             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4914                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4915                 while(piecesLeft[p] >= 2) {
4916                     AddOnePiece(board, p, 0, LITE);
4917                     AddOnePiece(board, p, 0, DARK);
4918                 }
4919                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4920             }
4921
4922         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4923             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4924             // but we leave King and Rooks for last, to possibly obey FRC restriction
4925             if(p == (int)WhiteRook) continue;
4926             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4927             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4928         }
4929
4930         // now everything is placed, except perhaps King (Unicorn) and Rooks
4931
4932         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4933             // Last King gets castling rights
4934             while(piecesLeft[(int)WhiteUnicorn]) {
4935                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4936                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4937             }
4938
4939             while(piecesLeft[(int)WhiteKing]) {
4940                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4941                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4942             }
4943
4944
4945         } else {
4946             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4947             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4948         }
4949
4950         // Only Rooks can be left; simply place them all
4951         while(piecesLeft[(int)WhiteRook]) {
4952                 i = put(board, WhiteRook, 0, 0, ANY);
4953                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4954                         if(first) {
4955                                 first=0;
4956                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4957                         }
4958                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4959                 }
4960         }
4961         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4962             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4963         }
4964
4965         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4966 }
4967
4968 int SetCharTable( char *table, const char * map )
4969 /* [HGM] moved here from winboard.c because of its general usefulness */
4970 /*       Basically a safe strcpy that uses the last character as King */
4971 {
4972     int result = FALSE; int NrPieces;
4973
4974     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4975                     && NrPieces >= 12 && !(NrPieces&1)) {
4976         int i; /* [HGM] Accept even length from 12 to 34 */
4977
4978         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4979         for( i=0; i<NrPieces/2-1; i++ ) {
4980             table[i] = map[i];
4981             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4982         }
4983         table[(int) WhiteKing]  = map[NrPieces/2-1];
4984         table[(int) BlackKing]  = map[NrPieces-1];
4985
4986         result = TRUE;
4987     }
4988
4989     return result;
4990 }
4991
4992 void Prelude(Board board)
4993 {       // [HGM] superchess: random selection of exo-pieces
4994         int i, j, k; ChessSquare p; 
4995         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4996
4997         GetPositionNumber(); // use FRC position number
4998
4999         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5000             SetCharTable(pieceToChar, appData.pieceToCharTable);
5001             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5002                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5003         }
5004
5005         j = seed%4;                 seed /= 4; 
5006         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5007         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5008         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5009         j = seed%3 + (seed%3 >= j); seed /= 3; 
5010         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5011         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5012         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5013         j = seed%3;                 seed /= 3; 
5014         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5015         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5016         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5017         j = seed%2 + (seed%2 >= j); seed /= 2; 
5018         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5019         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5020         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5021         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5022         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5023         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5024         put(board, exoPieces[0],    0, 0, ANY);
5025         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5026 }
5027
5028 void
5029 InitPosition(redraw)
5030      int redraw;
5031 {
5032     ChessSquare (* pieces)[BOARD_FILES];
5033     int i, j, pawnRow, overrule,
5034     oldx = gameInfo.boardWidth,
5035     oldy = gameInfo.boardHeight,
5036     oldh = gameInfo.holdingsWidth,
5037     oldv = gameInfo.variant;
5038
5039     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5040
5041     /* [AS] Initialize pv info list [HGM] and game status */
5042     {
5043         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5044             pvInfoList[i].depth = 0;
5045             boards[i][EP_STATUS] = EP_NONE;
5046             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5047         }
5048
5049         initialRulePlies = 0; /* 50-move counter start */
5050
5051         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5052         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5053     }
5054
5055     
5056     /* [HGM] logic here is completely changed. In stead of full positions */
5057     /* the initialized data only consist of the two backranks. The switch */
5058     /* selects which one we will use, which is than copied to the Board   */
5059     /* initialPosition, which for the rest is initialized by Pawns and    */
5060     /* empty squares. This initial position is then copied to boards[0],  */
5061     /* possibly after shuffling, so that it remains available.            */
5062
5063     gameInfo.holdingsWidth = 0; /* default board sizes */
5064     gameInfo.boardWidth    = 8;
5065     gameInfo.boardHeight   = 8;
5066     gameInfo.holdingsSize  = 0;
5067     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5068     for(i=0; i<BOARD_FILES-2; i++)
5069       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5070     initialPosition[EP_STATUS] = EP_NONE;
5071     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5072
5073     switch (gameInfo.variant) {
5074     case VariantFischeRandom:
5075       shuffleOpenings = TRUE;
5076     default:
5077       pieces = FIDEArray;
5078       break;
5079     case VariantShatranj:
5080       pieces = ShatranjArray;
5081       nrCastlingRights = 0;
5082       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5083       break;
5084     case VariantMakruk:
5085       pieces = makrukArray;
5086       nrCastlingRights = 0;
5087       startedFromSetupPosition = TRUE;
5088       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5089       break;
5090     case VariantTwoKings:
5091       pieces = twoKingsArray;
5092       break;
5093     case VariantCapaRandom:
5094       shuffleOpenings = TRUE;
5095     case VariantCapablanca:
5096       pieces = CapablancaArray;
5097       gameInfo.boardWidth = 10;
5098       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5099       break;
5100     case VariantGothic:
5101       pieces = GothicArray;
5102       gameInfo.boardWidth = 10;
5103       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5104       break;
5105     case VariantJanus:
5106       pieces = JanusArray;
5107       gameInfo.boardWidth = 10;
5108       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5109       nrCastlingRights = 6;
5110         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5111         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5112         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5113         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5114         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5115         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5116       break;
5117     case VariantFalcon:
5118       pieces = FalconArray;
5119       gameInfo.boardWidth = 10;
5120       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5121       break;
5122     case VariantXiangqi:
5123       pieces = XiangqiArray;
5124       gameInfo.boardWidth  = 9;
5125       gameInfo.boardHeight = 10;
5126       nrCastlingRights = 0;
5127       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5128       break;
5129     case VariantShogi:
5130       pieces = ShogiArray;
5131       gameInfo.boardWidth  = 9;
5132       gameInfo.boardHeight = 9;
5133       gameInfo.holdingsSize = 7;
5134       nrCastlingRights = 0;
5135       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5136       break;
5137     case VariantCourier:
5138       pieces = CourierArray;
5139       gameInfo.boardWidth  = 12;
5140       nrCastlingRights = 0;
5141       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5142       break;
5143     case VariantKnightmate:
5144       pieces = KnightmateArray;
5145       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5146       break;
5147     case VariantFairy:
5148       pieces = fairyArray;
5149       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5150       break;
5151     case VariantGreat:
5152       pieces = GreatArray;
5153       gameInfo.boardWidth = 10;
5154       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5155       gameInfo.holdingsSize = 8;
5156       break;
5157     case VariantSuper:
5158       pieces = FIDEArray;
5159       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5160       gameInfo.holdingsSize = 8;
5161       startedFromSetupPosition = TRUE;
5162       break;
5163     case VariantCrazyhouse:
5164     case VariantBughouse:
5165       pieces = FIDEArray;
5166       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5167       gameInfo.holdingsSize = 5;
5168       break;
5169     case VariantWildCastle:
5170       pieces = FIDEArray;
5171       /* !!?shuffle with kings guaranteed to be on d or e file */
5172       shuffleOpenings = 1;
5173       break;
5174     case VariantNoCastle:
5175       pieces = FIDEArray;
5176       nrCastlingRights = 0;
5177       /* !!?unconstrained back-rank shuffle */
5178       shuffleOpenings = 1;
5179       break;
5180     }
5181
5182     overrule = 0;
5183     if(appData.NrFiles >= 0) {
5184         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5185         gameInfo.boardWidth = appData.NrFiles;
5186     }
5187     if(appData.NrRanks >= 0) {
5188         gameInfo.boardHeight = appData.NrRanks;
5189     }
5190     if(appData.holdingsSize >= 0) {
5191         i = appData.holdingsSize;
5192         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5193         gameInfo.holdingsSize = i;
5194     }
5195     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5196     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5197         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5198
5199     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5200     if(pawnRow < 1) pawnRow = 1;
5201     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5202
5203     /* User pieceToChar list overrules defaults */
5204     if(appData.pieceToCharTable != NULL)
5205         SetCharTable(pieceToChar, appData.pieceToCharTable);
5206
5207     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5208
5209         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5210             s = (ChessSquare) 0; /* account holding counts in guard band */
5211         for( i=0; i<BOARD_HEIGHT; i++ )
5212             initialPosition[i][j] = s;
5213
5214         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5215         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5216         initialPosition[pawnRow][j] = WhitePawn;
5217         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5218         if(gameInfo.variant == VariantXiangqi) {
5219             if(j&1) {
5220                 initialPosition[pawnRow][j] = 
5221                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5222                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5223                    initialPosition[2][j] = WhiteCannon;
5224                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5225                 }
5226             }
5227         }
5228         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5229     }
5230     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5231
5232             j=BOARD_LEFT+1;
5233             initialPosition[1][j] = WhiteBishop;
5234             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5235             j=BOARD_RGHT-2;
5236             initialPosition[1][j] = WhiteRook;
5237             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5238     }
5239
5240     if( nrCastlingRights == -1) {
5241         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5242         /*       This sets default castling rights from none to normal corners   */
5243         /* Variants with other castling rights must set them themselves above    */
5244         nrCastlingRights = 6;
5245        
5246         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5247         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5248         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5249         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5250         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5251         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5252      }
5253
5254      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5255      if(gameInfo.variant == VariantGreat) { // promotion commoners
5256         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5257         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5258         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5259         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5260      }
5261   if (appData.debugMode) {
5262     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5263   }
5264     if(shuffleOpenings) {
5265         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5266         startedFromSetupPosition = TRUE;
5267     }
5268     if(startedFromPositionFile) {
5269       /* [HGM] loadPos: use PositionFile for every new game */
5270       CopyBoard(initialPosition, filePosition);
5271       for(i=0; i<nrCastlingRights; i++)
5272           initialRights[i] = filePosition[CASTLING][i];
5273       startedFromSetupPosition = TRUE;
5274     }
5275
5276     CopyBoard(boards[0], initialPosition);
5277
5278     if(oldx != gameInfo.boardWidth ||
5279        oldy != gameInfo.boardHeight ||
5280        oldh != gameInfo.holdingsWidth
5281 #ifdef GOTHIC
5282        || oldv == VariantGothic ||        // For licensing popups
5283        gameInfo.variant == VariantGothic
5284 #endif
5285 #ifdef FALCON
5286        || oldv == VariantFalcon ||
5287        gameInfo.variant == VariantFalcon
5288 #endif
5289                                          )
5290             InitDrawingSizes(-2 ,0);
5291
5292     if (redraw)
5293       DrawPosition(TRUE, boards[currentMove]);
5294 }
5295
5296 void
5297 SendBoard(cps, moveNum)
5298      ChessProgramState *cps;
5299      int moveNum;
5300 {
5301     char message[MSG_SIZ];
5302     
5303     if (cps->useSetboard) {
5304       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5305       sprintf(message, "setboard %s\n", fen);
5306       SendToProgram(message, cps);
5307       free(fen);
5308
5309     } else {
5310       ChessSquare *bp;
5311       int i, j;
5312       /* Kludge to set black to move, avoiding the troublesome and now
5313        * deprecated "black" command.
5314        */
5315       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5316
5317       SendToProgram("edit\n", cps);
5318       SendToProgram("#\n", cps);
5319       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5320         bp = &boards[moveNum][i][BOARD_LEFT];
5321         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5322           if ((int) *bp < (int) BlackPawn) {
5323             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5324                     AAA + j, ONE + i);
5325             if(message[0] == '+' || message[0] == '~') {
5326                 sprintf(message, "%c%c%c+\n",
5327                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5328                         AAA + j, ONE + i);
5329             }
5330             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5331                 message[1] = BOARD_RGHT   - 1 - j + '1';
5332                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5333             }
5334             SendToProgram(message, cps);
5335           }
5336         }
5337       }
5338     
5339       SendToProgram("c\n", cps);
5340       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5341         bp = &boards[moveNum][i][BOARD_LEFT];
5342         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5343           if (((int) *bp != (int) EmptySquare)
5344               && ((int) *bp >= (int) BlackPawn)) {
5345             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5346                     AAA + j, ONE + i);
5347             if(message[0] == '+' || message[0] == '~') {
5348                 sprintf(message, "%c%c%c+\n",
5349                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5350                         AAA + j, ONE + i);
5351             }
5352             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5353                 message[1] = BOARD_RGHT   - 1 - j + '1';
5354                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5355             }
5356             SendToProgram(message, cps);
5357           }
5358         }
5359       }
5360     
5361       SendToProgram(".\n", cps);
5362     }
5363     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5364 }
5365
5366 static int autoQueen; // [HGM] oneclick
5367
5368 int
5369 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5370 {
5371     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5372     /* [HGM] add Shogi promotions */
5373     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5374     ChessSquare piece;
5375     ChessMove moveType;
5376     Boolean premove;
5377
5378     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5379     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5380
5381     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5382       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5383         return FALSE;
5384
5385     piece = boards[currentMove][fromY][fromX];
5386     if(gameInfo.variant == VariantShogi) {
5387         promotionZoneSize = 3;
5388         highestPromotingPiece = (int)WhiteFerz;
5389     } else if(gameInfo.variant == VariantMakruk) {
5390         promotionZoneSize = 3;
5391     }
5392
5393     // next weed out all moves that do not touch the promotion zone at all
5394     if((int)piece >= BlackPawn) {
5395         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5396              return FALSE;
5397         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5398     } else {
5399         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5400            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5401     }
5402
5403     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5404
5405     // weed out mandatory Shogi promotions
5406     if(gameInfo.variant == VariantShogi) {
5407         if(piece >= BlackPawn) {
5408             if(toY == 0 && piece == BlackPawn ||
5409                toY == 0 && piece == BlackQueen ||
5410                toY <= 1 && piece == BlackKnight) {
5411                 *promoChoice = '+';
5412                 return FALSE;
5413             }
5414         } else {
5415             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5416                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5417                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5418                 *promoChoice = '+';
5419                 return FALSE;
5420             }
5421         }
5422     }
5423
5424     // weed out obviously illegal Pawn moves
5425     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5426         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5427         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5428         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5429         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5430         // note we are not allowed to test for valid (non-)capture, due to premove
5431     }
5432
5433     // we either have a choice what to promote to, or (in Shogi) whether to promote
5434     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5435         *promoChoice = PieceToChar(BlackFerz);  // no choice
5436         return FALSE;
5437     }
5438     if(autoQueen) { // predetermined
5439         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5440              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5441         else *promoChoice = PieceToChar(BlackQueen);
5442         return FALSE;
5443     }
5444
5445     // suppress promotion popup on illegal moves that are not premoves
5446     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5447               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5448     if(appData.testLegality && !premove) {
5449         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5450                         fromY, fromX, toY, toX, NULLCHAR);
5451         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5452            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5453             return FALSE;
5454     }
5455
5456     return TRUE;
5457 }
5458
5459 int
5460 InPalace(row, column)
5461      int row, column;
5462 {   /* [HGM] for Xiangqi */
5463     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5464          column < (BOARD_WIDTH + 4)/2 &&
5465          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5466     return FALSE;
5467 }
5468
5469 int
5470 PieceForSquare (x, y)
5471      int x;
5472      int y;
5473 {
5474   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5475      return -1;
5476   else
5477      return boards[currentMove][y][x];
5478 }
5479
5480 int
5481 OKToStartUserMove(x, y)
5482      int x, y;
5483 {
5484     ChessSquare from_piece;
5485     int white_piece;
5486
5487     if (matchMode) return FALSE;
5488     if (gameMode == EditPosition) return TRUE;
5489
5490     if (x >= 0 && y >= 0)
5491       from_piece = boards[currentMove][y][x];
5492     else
5493       from_piece = EmptySquare;
5494
5495     if (from_piece == EmptySquare) return FALSE;
5496
5497     white_piece = (int)from_piece >= (int)WhitePawn &&
5498       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5499
5500     switch (gameMode) {
5501       case PlayFromGameFile:
5502       case AnalyzeFile:
5503       case TwoMachinesPlay:
5504       case EndOfGame:
5505         return FALSE;
5506
5507       case IcsObserving:
5508       case IcsIdle:
5509         return FALSE;
5510
5511       case MachinePlaysWhite:
5512       case IcsPlayingBlack:
5513         if (appData.zippyPlay) return FALSE;
5514         if (white_piece) {
5515             DisplayMoveError(_("You are playing Black"));
5516             return FALSE;
5517         }
5518         break;
5519
5520       case MachinePlaysBlack:
5521       case IcsPlayingWhite:
5522         if (appData.zippyPlay) return FALSE;
5523         if (!white_piece) {
5524             DisplayMoveError(_("You are playing White"));
5525             return FALSE;
5526         }
5527         break;
5528
5529       case EditGame:
5530         if (!white_piece && WhiteOnMove(currentMove)) {
5531             DisplayMoveError(_("It is White's turn"));
5532             return FALSE;
5533         }           
5534         if (white_piece && !WhiteOnMove(currentMove)) {
5535             DisplayMoveError(_("It is Black's turn"));
5536             return FALSE;
5537         }           
5538         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5539             /* Editing correspondence game history */
5540             /* Could disallow this or prompt for confirmation */
5541             cmailOldMove = -1;
5542         }
5543         break;
5544
5545       case BeginningOfGame:
5546         if (appData.icsActive) return FALSE;
5547         if (!appData.noChessProgram) {
5548             if (!white_piece) {
5549                 DisplayMoveError(_("You are playing White"));
5550                 return FALSE;
5551             }
5552         }
5553         break;
5554         
5555       case Training:
5556         if (!white_piece && WhiteOnMove(currentMove)) {
5557             DisplayMoveError(_("It is White's turn"));
5558             return FALSE;
5559         }           
5560         if (white_piece && !WhiteOnMove(currentMove)) {
5561             DisplayMoveError(_("It is Black's turn"));
5562             return FALSE;
5563         }           
5564         break;
5565
5566       default:
5567       case IcsExamining:
5568         break;
5569     }
5570     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5571         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5572         && gameMode != AnalyzeFile && gameMode != Training) {
5573         DisplayMoveError(_("Displayed position is not current"));
5574         return FALSE;
5575     }
5576     return TRUE;
5577 }
5578
5579 Boolean
5580 OnlyMove(int *x, int *y, Boolean captures) {
5581     DisambiguateClosure cl;
5582     if (appData.zippyPlay) return FALSE;
5583     switch(gameMode) {
5584       case MachinePlaysBlack:
5585       case IcsPlayingWhite:
5586       case BeginningOfGame:
5587         if(!WhiteOnMove(currentMove)) return FALSE;
5588         break;
5589       case MachinePlaysWhite:
5590       case IcsPlayingBlack:
5591         if(WhiteOnMove(currentMove)) return FALSE;
5592         break;
5593       default:
5594         return FALSE;
5595     }
5596     cl.pieceIn = EmptySquare; 
5597     cl.rfIn = *y;
5598     cl.ffIn = *x;
5599     cl.rtIn = -1;
5600     cl.ftIn = -1;
5601     cl.promoCharIn = NULLCHAR;
5602     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5603     if( cl.kind == NormalMove ||
5604         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5605         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5606         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5607         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5608       fromX = cl.ff;
5609       fromY = cl.rf;
5610       *x = cl.ft;
5611       *y = cl.rt;
5612       return TRUE;
5613     }
5614     if(cl.kind != ImpossibleMove) return FALSE;
5615     cl.pieceIn = EmptySquare;
5616     cl.rfIn = -1;
5617     cl.ffIn = -1;
5618     cl.rtIn = *y;
5619     cl.ftIn = *x;
5620     cl.promoCharIn = NULLCHAR;
5621     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5622     if( cl.kind == NormalMove ||
5623         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5624         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5625         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5626         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5627       fromX = cl.ff;
5628       fromY = cl.rf;
5629       *x = cl.ft;
5630       *y = cl.rt;
5631       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5632       return TRUE;
5633     }
5634     return FALSE;
5635 }
5636
5637 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5638 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5639 int lastLoadGameUseList = FALSE;
5640 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5641 ChessMove lastLoadGameStart = (ChessMove) 0;
5642
5643 ChessMove
5644 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5645      int fromX, fromY, toX, toY;
5646      int promoChar;
5647      Boolean captureOwn;
5648 {
5649     ChessMove moveType;
5650     ChessSquare pdown, pup;
5651
5652     /* Check if the user is playing in turn.  This is complicated because we
5653        let the user "pick up" a piece before it is his turn.  So the piece he
5654        tried to pick up may have been captured by the time he puts it down!
5655        Therefore we use the color the user is supposed to be playing in this
5656        test, not the color of the piece that is currently on the starting
5657        square---except in EditGame mode, where the user is playing both
5658        sides; fortunately there the capture race can't happen.  (It can
5659        now happen in IcsExamining mode, but that's just too bad.  The user
5660        will get a somewhat confusing message in that case.)
5661        */
5662
5663     switch (gameMode) {
5664       case PlayFromGameFile:
5665       case AnalyzeFile:
5666       case TwoMachinesPlay:
5667       case EndOfGame:
5668       case IcsObserving:
5669       case IcsIdle:
5670         /* We switched into a game mode where moves are not accepted,
5671            perhaps while the mouse button was down. */
5672         return ImpossibleMove;
5673
5674       case MachinePlaysWhite:
5675         /* User is moving for Black */
5676         if (WhiteOnMove(currentMove)) {
5677             DisplayMoveError(_("It is White's turn"));
5678             return ImpossibleMove;
5679         }
5680         break;
5681
5682       case MachinePlaysBlack:
5683         /* User is moving for White */
5684         if (!WhiteOnMove(currentMove)) {
5685             DisplayMoveError(_("It is Black's turn"));
5686             return ImpossibleMove;
5687         }
5688         break;
5689
5690       case EditGame:
5691       case IcsExamining:
5692       case BeginningOfGame:
5693       case AnalyzeMode:
5694       case Training:
5695         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5696             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5697             /* User is moving for Black */
5698             if (WhiteOnMove(currentMove)) {
5699                 DisplayMoveError(_("It is White's turn"));
5700                 return ImpossibleMove;
5701             }
5702         } else {
5703             /* User is moving for White */
5704             if (!WhiteOnMove(currentMove)) {
5705                 DisplayMoveError(_("It is Black's turn"));
5706                 return ImpossibleMove;
5707             }
5708         }
5709         break;
5710
5711       case IcsPlayingBlack:
5712         /* User is moving for Black */
5713         if (WhiteOnMove(currentMove)) {
5714             if (!appData.premove) {
5715                 DisplayMoveError(_("It is White's turn"));
5716             } else if (toX >= 0 && toY >= 0) {
5717                 premoveToX = toX;
5718                 premoveToY = toY;
5719                 premoveFromX = fromX;
5720                 premoveFromY = fromY;
5721                 premovePromoChar = promoChar;
5722                 gotPremove = 1;
5723                 if (appData.debugMode) 
5724                     fprintf(debugFP, "Got premove: fromX %d,"
5725                             "fromY %d, toX %d, toY %d\n",
5726                             fromX, fromY, toX, toY);
5727             }
5728             return ImpossibleMove;
5729         }
5730         break;
5731
5732       case IcsPlayingWhite:
5733         /* User is moving for White */
5734         if (!WhiteOnMove(currentMove)) {
5735             if (!appData.premove) {
5736                 DisplayMoveError(_("It is Black's turn"));
5737             } else if (toX >= 0 && toY >= 0) {
5738                 premoveToX = toX;
5739                 premoveToY = toY;
5740                 premoveFromX = fromX;
5741                 premoveFromY = fromY;
5742                 premovePromoChar = promoChar;
5743                 gotPremove = 1;
5744                 if (appData.debugMode) 
5745                     fprintf(debugFP, "Got premove: fromX %d,"
5746                             "fromY %d, toX %d, toY %d\n",
5747                             fromX, fromY, toX, toY);
5748             }
5749             return ImpossibleMove;
5750         }
5751         break;
5752
5753       default:
5754         break;
5755
5756       case EditPosition:
5757         /* EditPosition, empty square, or different color piece;
5758            click-click move is possible */
5759         if (toX == -2 || toY == -2) {
5760             boards[0][fromY][fromX] = EmptySquare;
5761             return AmbiguousMove;
5762         } else if (toX >= 0 && toY >= 0) {
5763             boards[0][toY][toX] = boards[0][fromY][fromX];
5764             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5765                 if(boards[0][fromY][0] != EmptySquare) {
5766                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5767                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5768                 }
5769             } else
5770             if(fromX == BOARD_RGHT+1) {
5771                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5772                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5773                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5774                 }
5775             } else
5776             boards[0][fromY][fromX] = EmptySquare;
5777             return AmbiguousMove;
5778         }
5779         return ImpossibleMove;
5780     }
5781
5782     if(toX < 0 || toY < 0) return ImpossibleMove;
5783     pdown = boards[currentMove][fromY][fromX];
5784     pup = boards[currentMove][toY][toX];
5785
5786     /* [HGM] If move started in holdings, it means a drop */
5787     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5788          if( pup != EmptySquare ) return ImpossibleMove;
5789          if(appData.testLegality) {
5790              /* it would be more logical if LegalityTest() also figured out
5791               * which drops are legal. For now we forbid pawns on back rank.
5792               * Shogi is on its own here...
5793               */
5794              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5795                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5796                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5797          }
5798          return WhiteDrop; /* Not needed to specify white or black yet */
5799     }
5800
5801     /* [HGM] always test for legality, to get promotion info */
5802     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5803                                          fromY, fromX, toY, toX, promoChar);
5804     /* [HGM] but possibly ignore an IllegalMove result */
5805     if (appData.testLegality) {
5806         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5807             DisplayMoveError(_("Illegal move"));
5808             return ImpossibleMove;
5809         }
5810     }
5811
5812     return moveType;
5813     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5814        function is made into one that returns an OK move type if FinishMove
5815        should be called. This to give the calling driver routine the
5816        opportunity to finish the userMove input with a promotion popup,
5817        without bothering the user with this for invalid or illegal moves */
5818
5819 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5820 }
5821
5822 /* Common tail of UserMoveEvent and DropMenuEvent */
5823 int
5824 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5825      ChessMove moveType;
5826      int fromX, fromY, toX, toY;
5827      /*char*/int promoChar;
5828 {
5829     char *bookHit = 0;
5830
5831     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5832         // [HGM] superchess: suppress promotions to non-available piece
5833         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5834         if(WhiteOnMove(currentMove)) {
5835             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5836         } else {
5837             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5838         }
5839     }
5840
5841     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5842        move type in caller when we know the move is a legal promotion */
5843     if(moveType == NormalMove && promoChar)
5844         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5845
5846     /* [HGM] convert drag-and-drop piece drops to standard form */
5847     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5848          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5849            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5850                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5851            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5852            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5853            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5854            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5855          fromY = DROP_RANK;
5856     }
5857
5858     /* [HGM] <popupFix> The following if has been moved here from
5859        UserMoveEvent(). Because it seemed to belong here (why not allow
5860        piece drops in training games?), and because it can only be
5861        performed after it is known to what we promote. */
5862     if (gameMode == Training) {
5863       /* compare the move played on the board to the next move in the
5864        * game. If they match, display the move and the opponent's response. 
5865        * If they don't match, display an error message.
5866        */
5867       int saveAnimate;
5868       Board testBoard;
5869       CopyBoard(testBoard, boards[currentMove]);
5870       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5871
5872       if (CompareBoards(testBoard, boards[currentMove+1])) {
5873         ForwardInner(currentMove+1);
5874
5875         /* Autoplay the opponent's response.
5876          * if appData.animate was TRUE when Training mode was entered,
5877          * the response will be animated.
5878          */
5879         saveAnimate = appData.animate;
5880         appData.animate = animateTraining;
5881         ForwardInner(currentMove+1);
5882         appData.animate = saveAnimate;
5883
5884         /* check for the end of the game */
5885         if (currentMove >= forwardMostMove) {
5886           gameMode = PlayFromGameFile;
5887           ModeHighlight();
5888           SetTrainingModeOff();
5889           DisplayInformation(_("End of game"));
5890         }
5891       } else {
5892         DisplayError(_("Incorrect move"), 0);
5893       }
5894       return 1;
5895     }
5896
5897   /* Ok, now we know that the move is good, so we can kill
5898      the previous line in Analysis Mode */
5899   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5900                                 && currentMove < forwardMostMove) {
5901     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5902   }
5903
5904   /* If we need the chess program but it's dead, restart it */
5905   ResurrectChessProgram();
5906
5907   /* A user move restarts a paused game*/
5908   if (pausing)
5909     PauseEvent();
5910
5911   thinkOutput[0] = NULLCHAR;
5912
5913   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5914
5915   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5916
5917   if (gameMode == BeginningOfGame) {
5918     if (appData.noChessProgram) {
5919       gameMode = EditGame;
5920       SetGameInfo();
5921     } else {
5922       char buf[MSG_SIZ];
5923       gameMode = MachinePlaysBlack;
5924       StartClocks();
5925       SetGameInfo();
5926       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5927       DisplayTitle(buf);
5928       if (first.sendName) {
5929         sprintf(buf, "name %s\n", gameInfo.white);
5930         SendToProgram(buf, &first);
5931       }
5932       StartClocks();
5933     }
5934     ModeHighlight();
5935   }
5936
5937   /* Relay move to ICS or chess engine */
5938   if (appData.icsActive) {
5939     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5940         gameMode == IcsExamining) {
5941       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5942         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5943         SendToICS("draw ");
5944         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5945       }
5946       // also send plain move, in case ICS does not understand atomic claims
5947       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5948       ics_user_moved = 1;
5949     }
5950   } else {
5951     if (first.sendTime && (gameMode == BeginningOfGame ||
5952                            gameMode == MachinePlaysWhite ||
5953                            gameMode == MachinePlaysBlack)) {
5954       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5955     }
5956     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5957          // [HGM] book: if program might be playing, let it use book
5958         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5959         first.maybeThinking = TRUE;
5960     } else SendMoveToProgram(forwardMostMove-1, &first);
5961     if (currentMove == cmailOldMove + 1) {
5962       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5963     }
5964   }
5965
5966   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5967
5968   switch (gameMode) {
5969   case EditGame:
5970     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5971     case MT_NONE:
5972     case MT_CHECK:
5973       break;
5974     case MT_CHECKMATE:
5975     case MT_STAINMATE:
5976       if (WhiteOnMove(currentMove)) {
5977         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5978       } else {
5979         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5980       }
5981       break;
5982     case MT_STALEMATE:
5983       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5984       break;
5985     }
5986     break;
5987     
5988   case MachinePlaysBlack:
5989   case MachinePlaysWhite:
5990     /* disable certain menu options while machine is thinking */
5991     SetMachineThinkingEnables();
5992     break;
5993
5994   default:
5995     break;
5996   }
5997
5998   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5999         
6000   if(bookHit) { // [HGM] book: simulate book reply
6001         static char bookMove[MSG_SIZ]; // a bit generous?
6002
6003         programStats.nodes = programStats.depth = programStats.time = 
6004         programStats.score = programStats.got_only_move = 0;
6005         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6006
6007         strcpy(bookMove, "move ");
6008         strcat(bookMove, bookHit);
6009         HandleMachineMove(bookMove, &first);
6010   }
6011   return 1;
6012 }
6013
6014 void
6015 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6016      int fromX, fromY, toX, toY;
6017      int promoChar;
6018 {
6019     /* [HGM] This routine was added to allow calling of its two logical
6020        parts from other modules in the old way. Before, UserMoveEvent()
6021        automatically called FinishMove() if the move was OK, and returned
6022        otherwise. I separated the two, in order to make it possible to
6023        slip a promotion popup in between. But that it always needs two
6024        calls, to the first part, (now called UserMoveTest() ), and to
6025        FinishMove if the first part succeeded. Calls that do not need
6026        to do anything in between, can call this routine the old way. 
6027     */
6028     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6029 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6030     if(moveType == AmbiguousMove)
6031         DrawPosition(FALSE, boards[currentMove]);
6032     else if(moveType != ImpossibleMove && moveType != Comment)
6033         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6034 }
6035
6036 void
6037 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6038      Board board;
6039      int flags;
6040      ChessMove kind;
6041      int rf, ff, rt, ft;
6042      VOIDSTAR closure;
6043 {
6044     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6045     Markers *m = (Markers *) closure;
6046     if(rf == fromY && ff == fromX)
6047         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6048                          || kind == WhiteCapturesEnPassant
6049                          || kind == BlackCapturesEnPassant);
6050     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6051 }
6052
6053 void
6054 MarkTargetSquares(int clear)
6055 {
6056   int x, y;
6057   if(!appData.markers || !appData.highlightDragging || 
6058      !appData.testLegality || gameMode == EditPosition) return;
6059   if(clear) {
6060     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6061   } else {
6062     int capt = 0;
6063     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6064     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6065       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6066       if(capt)
6067       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6068     }
6069   }
6070   DrawPosition(TRUE, NULL);
6071 }
6072
6073 void LeftClick(ClickType clickType, int xPix, int yPix)
6074 {
6075     int x, y;
6076     Boolean saveAnimate;
6077     static int second = 0, promotionChoice = 0;
6078     char promoChoice = NULLCHAR;
6079
6080     if(appData.seekGraph && appData.icsActive && loggedOn &&
6081         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6082         SeekGraphClick(clickType, xPix, yPix, 0);
6083         return;
6084     }
6085
6086     if (clickType == Press) ErrorPopDown();
6087     MarkTargetSquares(1);
6088
6089     x = EventToSquare(xPix, BOARD_WIDTH);
6090     y = EventToSquare(yPix, BOARD_HEIGHT);
6091     if (!flipView && y >= 0) {
6092         y = BOARD_HEIGHT - 1 - y;
6093     }
6094     if (flipView && x >= 0) {
6095         x = BOARD_WIDTH - 1 - x;
6096     }
6097
6098     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6099         if(clickType == Release) return; // ignore upclick of click-click destination
6100         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6101         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6102         if(gameInfo.holdingsWidth && 
6103                 (WhiteOnMove(currentMove) 
6104                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6105                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6106             // click in right holdings, for determining promotion piece
6107             ChessSquare p = boards[currentMove][y][x];
6108             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6109             if(p != EmptySquare) {
6110                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6111                 fromX = fromY = -1;
6112                 return;
6113             }
6114         }
6115         DrawPosition(FALSE, boards[currentMove]);
6116         return;
6117     }
6118
6119     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6120     if(clickType == Press
6121             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6122               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6123               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6124         return;
6125
6126     autoQueen = appData.alwaysPromoteToQueen;
6127
6128     if (fromX == -1) {
6129       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6130         if (clickType == Press) {
6131             /* First square */
6132             if (OKToStartUserMove(x, y)) {
6133                 fromX = x;
6134                 fromY = y;
6135                 second = 0;
6136                 MarkTargetSquares(0);
6137                 DragPieceBegin(xPix, yPix);
6138                 if (appData.highlightDragging) {
6139                     SetHighlights(x, y, -1, -1);
6140                 }
6141             }
6142         }
6143         return;
6144       }
6145     }
6146
6147     /* fromX != -1 */
6148     if (clickType == Press && gameMode != EditPosition) {
6149         ChessSquare fromP;
6150         ChessSquare toP;
6151         int frc;
6152
6153         // ignore off-board to clicks
6154         if(y < 0 || x < 0) return;
6155
6156         /* Check if clicking again on the same color piece */
6157         fromP = boards[currentMove][fromY][fromX];
6158         toP = boards[currentMove][y][x];
6159         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6160         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6161              WhitePawn <= toP && toP <= WhiteKing &&
6162              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6163              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6164             (BlackPawn <= fromP && fromP <= BlackKing && 
6165              BlackPawn <= toP && toP <= BlackKing &&
6166              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6167              !(fromP == BlackKing && toP == BlackRook && frc))) {
6168             /* Clicked again on same color piece -- changed his mind */
6169             second = (x == fromX && y == fromY);
6170            if(!second || !OnlyMove(&x, &y, TRUE)) {
6171             if (appData.highlightDragging) {
6172                 SetHighlights(x, y, -1, -1);
6173             } else {
6174                 ClearHighlights();
6175             }
6176             if (OKToStartUserMove(x, y)) {
6177                 fromX = x;
6178                 fromY = y;
6179                 MarkTargetSquares(0);
6180                 DragPieceBegin(xPix, yPix);
6181             }
6182             return;
6183            }
6184         }
6185         // ignore clicks on holdings
6186         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6187     }
6188
6189     if (clickType == Release && x == fromX && y == fromY) {
6190         DragPieceEnd(xPix, yPix);
6191         if (appData.animateDragging) {
6192             /* Undo animation damage if any */
6193             DrawPosition(FALSE, NULL);
6194         }
6195         if (second) {
6196             /* Second up/down in same square; just abort move */
6197             second = 0;
6198             fromX = fromY = -1;
6199             ClearHighlights();
6200             gotPremove = 0;
6201             ClearPremoveHighlights();
6202         } else {
6203             /* First upclick in same square; start click-click mode */
6204             SetHighlights(x, y, -1, -1);
6205         }
6206         return;
6207     }
6208
6209     /* we now have a different from- and (possibly off-board) to-square */
6210     /* Completed move */
6211     toX = x;
6212     toY = y;
6213     saveAnimate = appData.animate;
6214     if (clickType == Press) {
6215         /* Finish clickclick move */
6216         if (appData.animate || appData.highlightLastMove) {
6217             SetHighlights(fromX, fromY, toX, toY);
6218         } else {
6219             ClearHighlights();
6220         }
6221     } else {
6222         /* Finish drag move */
6223         if (appData.highlightLastMove) {
6224             SetHighlights(fromX, fromY, toX, toY);
6225         } else {
6226             ClearHighlights();
6227         }
6228         DragPieceEnd(xPix, yPix);
6229         /* Don't animate move and drag both */
6230         appData.animate = FALSE;
6231     }
6232
6233     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6234     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6235         ChessSquare piece = boards[currentMove][fromY][fromX];
6236         if(gameMode == EditPosition && piece != EmptySquare &&
6237            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6238             int n;
6239              
6240             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6241                 n = PieceToNumber(piece - (int)BlackPawn);
6242                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6243                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6244                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6245             } else
6246             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6247                 n = PieceToNumber(piece);
6248                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6249                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6250                 boards[currentMove][n][BOARD_WIDTH-2]++;
6251             }
6252             boards[currentMove][fromY][fromX] = EmptySquare;
6253         }
6254         ClearHighlights();
6255         fromX = fromY = -1;
6256         DrawPosition(TRUE, boards[currentMove]);
6257         return;
6258     }
6259
6260     // off-board moves should not be highlighted
6261     if(x < 0 || x < 0) ClearHighlights();
6262
6263     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6264         SetHighlights(fromX, fromY, toX, toY);
6265         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6266             // [HGM] super: promotion to captured piece selected from holdings
6267             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6268             promotionChoice = TRUE;
6269             // kludge follows to temporarily execute move on display, without promoting yet
6270             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6271             boards[currentMove][toY][toX] = p;
6272             DrawPosition(FALSE, boards[currentMove]);
6273             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6274             boards[currentMove][toY][toX] = q;
6275             DisplayMessage("Click in holdings to choose piece", "");
6276             return;
6277         }
6278         PromotionPopUp();
6279     } else {
6280         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6281         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6282         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6283         fromX = fromY = -1;
6284     }
6285     appData.animate = saveAnimate;
6286     if (appData.animate || appData.animateDragging) {
6287         /* Undo animation damage if needed */
6288         DrawPosition(FALSE, NULL);
6289     }
6290 }
6291
6292 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6293 {   // front-end-free part taken out of PieceMenuPopup
6294     int whichMenu; int xSqr, ySqr;
6295
6296     if(seekGraphUp) { // [HGM] seekgraph
6297         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6298         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6299         return -2;
6300     }
6301
6302     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6303          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6304         if(action == Press)   { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
6305         if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
6306         return -2;
6307     }
6308
6309     xSqr = EventToSquare(x, BOARD_WIDTH);
6310     ySqr = EventToSquare(y, BOARD_HEIGHT);
6311     if (action == Release) UnLoadPV(); // [HGM] pv
6312     if (action != Press) return -2; // return code to be ignored
6313     switch (gameMode) {
6314       case IcsExamining:
6315         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6316       case EditPosition:
6317         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6318         if (xSqr < 0 || ySqr < 0) return -1;\r
6319         whichMenu = 0; // edit-position menu
6320         break;
6321       case IcsObserving:
6322         if(!appData.icsEngineAnalyze) return -1;
6323       case IcsPlayingWhite:
6324       case IcsPlayingBlack:
6325         if(!appData.zippyPlay) goto noZip;
6326       case AnalyzeMode:
6327       case AnalyzeFile:
6328       case MachinePlaysWhite:
6329       case MachinePlaysBlack:
6330       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6331         if (!appData.dropMenu) {
6332           LoadPV(x, y);
6333           return 2; // flag front-end to grab mouse events
6334         }
6335         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6336            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6337       case EditGame:
6338       noZip:
6339         if (xSqr < 0 || ySqr < 0) return -1;
6340         if (!appData.dropMenu || appData.testLegality &&
6341             gameInfo.variant != VariantBughouse &&
6342             gameInfo.variant != VariantCrazyhouse) return -1;
6343         whichMenu = 1; // drop menu
6344         break;
6345       default:
6346         return -1;
6347     }
6348
6349     if (((*fromX = xSqr) < 0) ||
6350         ((*fromY = ySqr) < 0)) {
6351         *fromX = *fromY = -1;
6352         return -1;
6353     }
6354     if (flipView)
6355       *fromX = BOARD_WIDTH - 1 - *fromX;
6356     else
6357       *fromY = BOARD_HEIGHT - 1 - *fromY;
6358
6359     return whichMenu;
6360 }
6361
6362 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6363 {
6364 //    char * hint = lastHint;
6365     FrontEndProgramStats stats;
6366
6367     stats.which = cps == &first ? 0 : 1;
6368     stats.depth = cpstats->depth;
6369     stats.nodes = cpstats->nodes;
6370     stats.score = cpstats->score;
6371     stats.time = cpstats->time;
6372     stats.pv = cpstats->movelist;
6373     stats.hint = lastHint;
6374     stats.an_move_index = 0;
6375     stats.an_move_count = 0;
6376
6377     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6378         stats.hint = cpstats->move_name;
6379         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6380         stats.an_move_count = cpstats->nr_moves;
6381     }
6382
6383     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6384
6385     SetProgramStats( &stats );
6386 }
6387
6388 int
6389 Adjudicate(ChessProgramState *cps)
6390 {       // [HGM] some adjudications useful with buggy engines
6391         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6392         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6393         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6394         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6395         int k, count = 0; static int bare = 1;
6396         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6397         Boolean canAdjudicate = !appData.icsActive;
6398
6399         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6400         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6401             if( appData.testLegality )
6402             {   /* [HGM] Some more adjudications for obstinate engines */
6403                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6404                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6405                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6406                 static int moveCount = 6;
6407                 ChessMove result;
6408                 char *reason = NULL;
6409
6410                 /* Count what is on board. */
6411                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6412                 {   ChessSquare p = boards[forwardMostMove][i][j];
6413                     int m=i;
6414
6415                     switch((int) p)
6416                     {   /* count B,N,R and other of each side */
6417                         case WhiteKing:
6418                         case BlackKing:
6419                              NrK++; break; // [HGM] atomic: count Kings
6420                         case WhiteKnight:
6421                              NrWN++; break;
6422                         case WhiteBishop:
6423                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6424                              bishopsColor |= 1 << ((i^j)&1);
6425                              NrWB++; break;
6426                         case BlackKnight:
6427                              NrBN++; break;
6428                         case BlackBishop:
6429                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6430                              bishopsColor |= 1 << ((i^j)&1);
6431                              NrBB++; break;
6432                         case WhiteRook:
6433                              NrWR++; break;
6434                         case BlackRook:
6435                              NrBR++; break;
6436                         case WhiteQueen:
6437                              NrWQ++; break;
6438                         case BlackQueen:
6439                              NrBQ++; break;
6440                         case EmptySquare: 
6441                              break;
6442                         case BlackPawn:
6443                              m = 7-i;
6444                         case WhitePawn:
6445                              PawnAdvance += m; NrPawns++;
6446                     }
6447                     NrPieces += (p != EmptySquare);
6448                     NrW += ((int)p < (int)BlackPawn);
6449                     if(gameInfo.variant == VariantXiangqi && 
6450                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6451                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6452                         NrW -= ((int)p < (int)BlackPawn);
6453                     }
6454                 }
6455
6456                 /* Some material-based adjudications that have to be made before stalemate test */
6457                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6458                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6459                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6460                      if(canAdjudicate && appData.checkMates) {
6461                          if(engineOpponent)
6462                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6463                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6464                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6465                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6466                          return 1;
6467                      }
6468                 }
6469
6470                 /* Bare King in Shatranj (loses) or Losers (wins) */
6471                 if( NrW == 1 || NrPieces - NrW == 1) {
6472                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6473                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6474                      if(canAdjudicate && appData.checkMates) {
6475                          if(engineOpponent)
6476                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6477                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6478                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6479                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6480                          return 1;
6481                      }
6482                   } else
6483                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6484                   {    /* bare King */
6485                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6486                         if(canAdjudicate && appData.checkMates) {
6487                             /* but only adjudicate if adjudication enabled */
6488                             if(engineOpponent)
6489                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6490                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6491                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6492                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6493                             return 1;
6494                         }
6495                   }
6496                 } else bare = 1;
6497
6498
6499             // don't wait for engine to announce game end if we can judge ourselves
6500             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6501               case MT_CHECK:
6502                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6503                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6504                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6505                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6506                             checkCnt++;
6507                         if(checkCnt >= 2) {
6508                             reason = "Xboard adjudication: 3rd check";
6509                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6510                             break;
6511                         }
6512                     }
6513                 }
6514               case MT_NONE:
6515               default:
6516                 break;
6517               case MT_STALEMATE:
6518               case MT_STAINMATE:
6519                 reason = "Xboard adjudication: Stalemate";
6520                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6521                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6522                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6523                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6524                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6525                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6526                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6527                                                                         EP_CHECKMATE : EP_WINS);
6528                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6529                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6530                 }
6531                 break;
6532               case MT_CHECKMATE:
6533                 reason = "Xboard adjudication: Checkmate";
6534                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6535                 break;
6536             }
6537
6538                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6539                     case EP_STALEMATE:
6540                         result = GameIsDrawn; break;
6541                     case EP_CHECKMATE:
6542                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6543                     case EP_WINS:
6544                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6545                     default:
6546                         result = (ChessMove) 0;
6547                 }
6548                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6549                     if(engineOpponent)
6550                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6551                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6552                     GameEnds( result, reason, GE_XBOARD );
6553                     return 1;
6554                 }
6555
6556                 /* Next absolutely insufficient mating material. */
6557                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6558                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6559                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6560                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6561                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6562
6563                      /* always flag draws, for judging claims */
6564                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6565
6566                      if(canAdjudicate && appData.materialDraws) {
6567                          /* but only adjudicate them if adjudication enabled */
6568                          if(engineOpponent) {
6569                            SendToProgram("force\n", engineOpponent); // suppress reply
6570                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6571                          }
6572                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6573                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6574                          return 1;
6575                      }
6576                 }
6577
6578                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6579                 if(NrPieces == 4 && 
6580                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6581                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6582                    || NrWN==2 || NrBN==2     /* KNNK */
6583                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6584                   ) ) {
6585                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6586                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6587                           if(engineOpponent) {
6588                             SendToProgram("force\n", engineOpponent); // suppress reply
6589                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6590                           }
6591                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6592                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6593                           return 1;
6594                      }
6595                 } else moveCount = 6;
6596             }
6597         }
6598           
6599         if (appData.debugMode) { int i;
6600             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6601                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6602                     appData.drawRepeats);
6603             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6604               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6605             
6606         }
6607
6608         // Repetition draws and 50-move rule can be applied independently of legality testing
6609
6610                 /* Check for rep-draws */
6611                 count = 0;
6612                 for(k = forwardMostMove-2;
6613                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6614                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6615                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6616                     k-=2)
6617                 {   int rights=0;
6618                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6619                         /* compare castling rights */
6620                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6621                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6622                                 rights++; /* King lost rights, while rook still had them */
6623                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6624                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6625                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6626                                    rights++; /* but at least one rook lost them */
6627                         }
6628                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6629                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6630                                 rights++; 
6631                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6632                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6633                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6634                                    rights++;
6635                         }
6636                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6637                             && appData.drawRepeats > 1) {
6638                              /* adjudicate after user-specified nr of repeats */
6639                              if(engineOpponent) {
6640                                SendToProgram("force\n", engineOpponent); // suppress reply
6641                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6642                              }
6643                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6644                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6645                                 // [HGM] xiangqi: check for forbidden perpetuals
6646                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6647                                 for(m=forwardMostMove; m>k; m-=2) {
6648                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6649                                         ourPerpetual = 0; // the current mover did not always check
6650                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6651                                         hisPerpetual = 0; // the opponent did not always check
6652                                 }
6653                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6654                                                                         ourPerpetual, hisPerpetual);
6655                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6656                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6657                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6658                                     return 1;
6659                                 }
6660                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6661                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6662                                 // Now check for perpetual chases
6663                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6664                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6665                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6666                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6667                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6668                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6669                                         return 1;
6670                                     }
6671                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6672                                         break; // Abort repetition-checking loop.
6673                                 }
6674                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6675                              }
6676                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6677                              return 1;
6678                         }
6679                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6680                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6681                     }
6682                 }
6683
6684                 /* Now we test for 50-move draws. Determine ply count */
6685                 count = forwardMostMove;
6686                 /* look for last irreversble move */
6687                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6688                     count--;
6689                 /* if we hit starting position, add initial plies */
6690                 if( count == backwardMostMove )
6691                     count -= initialRulePlies;
6692                 count = forwardMostMove - count; 
6693                 if( count >= 100)
6694                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6695                          /* this is used to judge if draw claims are legal */
6696                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6697                          if(engineOpponent) {
6698                            SendToProgram("force\n", engineOpponent); // suppress reply
6699                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6700                          }
6701                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6702                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6703                          return 1;
6704                 }
6705
6706                 /* if draw offer is pending, treat it as a draw claim
6707                  * when draw condition present, to allow engines a way to
6708                  * claim draws before making their move to avoid a race
6709                  * condition occurring after their move
6710                  */
6711                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6712                          char *p = NULL;
6713                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6714                              p = "Draw claim: 50-move rule";
6715                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6716                              p = "Draw claim: 3-fold repetition";
6717                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6718                              p = "Draw claim: insufficient mating material";
6719                          if( p != NULL && canAdjudicate) {
6720                              if(engineOpponent) {
6721                                SendToProgram("force\n", engineOpponent); // suppress reply
6722                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6723                              }
6724                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6725                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6726                              return 1;
6727                          }
6728                 }
6729
6730                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6731                     if(engineOpponent) {
6732                       SendToProgram("force\n", engineOpponent); // suppress reply
6733                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6734                     }
6735                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6736                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6737                     return 1;
6738                 }
6739         return 0;
6740 }
6741
6742 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6743 {   // [HGM] book: this routine intercepts moves to simulate book replies
6744     char *bookHit = NULL;
6745
6746     //first determine if the incoming move brings opponent into his book
6747     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6748         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6749     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6750     if(bookHit != NULL && !cps->bookSuspend) {
6751         // make sure opponent is not going to reply after receiving move to book position
6752         SendToProgram("force\n", cps);
6753         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6754     }
6755     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6756     // now arrange restart after book miss
6757     if(bookHit) {
6758         // after a book hit we never send 'go', and the code after the call to this routine
6759         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6760         char buf[MSG_SIZ];
6761         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6762         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6763         SendToProgram(buf, cps);
6764         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6765     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6766         SendToProgram("go\n", cps);
6767         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6768     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6769         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6770             SendToProgram("go\n", cps); 
6771         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6772     }
6773     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6774 }
6775
6776 char *savedMessage;
6777 ChessProgramState *savedState;
6778 void DeferredBookMove(void)
6779 {
6780         if(savedState->lastPing != savedState->lastPong)
6781                     ScheduleDelayedEvent(DeferredBookMove, 10);
6782         else
6783         HandleMachineMove(savedMessage, savedState);
6784 }
6785
6786 void
6787 HandleMachineMove(message, cps)
6788      char *message;
6789      ChessProgramState *cps;
6790 {
6791     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6792     char realname[MSG_SIZ];
6793     int fromX, fromY, toX, toY;
6794     ChessMove moveType;
6795     char promoChar;
6796     char *p;
6797     int machineWhite;
6798     char *bookHit;
6799
6800     cps->userError = 0;
6801
6802 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6803     /*
6804      * Kludge to ignore BEL characters
6805      */
6806     while (*message == '\007') message++;
6807
6808     /*
6809      * [HGM] engine debug message: ignore lines starting with '#' character
6810      */
6811     if(cps->debug && *message == '#') return;
6812
6813     /*
6814      * Look for book output
6815      */
6816     if (cps == &first && bookRequested) {
6817         if (message[0] == '\t' || message[0] == ' ') {
6818             /* Part of the book output is here; append it */
6819             strcat(bookOutput, message);
6820             strcat(bookOutput, "  \n");
6821             return;
6822         } else if (bookOutput[0] != NULLCHAR) {
6823             /* All of book output has arrived; display it */
6824             char *p = bookOutput;
6825             while (*p != NULLCHAR) {
6826                 if (*p == '\t') *p = ' ';
6827                 p++;
6828             }
6829             DisplayInformation(bookOutput);
6830             bookRequested = FALSE;
6831             /* Fall through to parse the current output */
6832         }
6833     }
6834
6835     /*
6836      * Look for machine move.
6837      */
6838     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6839         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6840     {
6841         /* This method is only useful on engines that support ping */
6842         if (cps->lastPing != cps->lastPong) {
6843           if (gameMode == BeginningOfGame) {
6844             /* Extra move from before last new; ignore */
6845             if (appData.debugMode) {
6846                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6847             }
6848           } else {
6849             if (appData.debugMode) {
6850                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6851                         cps->which, gameMode);
6852             }
6853
6854             SendToProgram("undo\n", cps);
6855           }
6856           return;
6857         }
6858
6859         switch (gameMode) {
6860           case BeginningOfGame:
6861             /* Extra move from before last reset; ignore */
6862             if (appData.debugMode) {
6863                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6864             }
6865             return;
6866
6867           case EndOfGame:
6868           case IcsIdle:
6869           default:
6870             /* Extra move after we tried to stop.  The mode test is
6871                not a reliable way of detecting this problem, but it's
6872                the best we can do on engines that don't support ping.
6873             */
6874             if (appData.debugMode) {
6875                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6876                         cps->which, gameMode);
6877             }
6878             SendToProgram("undo\n", cps);
6879             return;
6880
6881           case MachinePlaysWhite:
6882           case IcsPlayingWhite:
6883             machineWhite = TRUE;
6884             break;
6885
6886           case MachinePlaysBlack:
6887           case IcsPlayingBlack:
6888             machineWhite = FALSE;
6889             break;
6890
6891           case TwoMachinesPlay:
6892             machineWhite = (cps->twoMachinesColor[0] == 'w');
6893             break;
6894         }
6895         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6896             if (appData.debugMode) {
6897                 fprintf(debugFP,
6898                         "Ignoring move out of turn by %s, gameMode %d"
6899                         ", forwardMost %d\n",
6900                         cps->which, gameMode, forwardMostMove);
6901             }
6902             return;
6903         }
6904
6905     if (appData.debugMode) { int f = forwardMostMove;
6906         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6907                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6908                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6909     }
6910         if(cps->alphaRank) AlphaRank(machineMove, 4);
6911         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6912                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6913             /* Machine move could not be parsed; ignore it. */
6914             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6915                     machineMove, cps->which);
6916             DisplayError(buf1, 0);
6917             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6918                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6919             if (gameMode == TwoMachinesPlay) {
6920               GameEnds(machineWhite ? BlackWins : WhiteWins,
6921                        buf1, GE_XBOARD);
6922             }
6923             return;
6924         }
6925
6926         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6927         /* So we have to redo legality test with true e.p. status here,  */
6928         /* to make sure an illegal e.p. capture does not slip through,   */
6929         /* to cause a forfeit on a justified illegal-move complaint      */
6930         /* of the opponent.                                              */
6931         if( gameMode==TwoMachinesPlay && appData.testLegality
6932             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6933                                                               ) {
6934            ChessMove moveType;
6935            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6936                              fromY, fromX, toY, toX, promoChar);
6937             if (appData.debugMode) {
6938                 int i;
6939                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6940                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6941                 fprintf(debugFP, "castling rights\n");
6942             }
6943             if(moveType == IllegalMove) {
6944                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6945                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6946                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6947                            buf1, GE_XBOARD);
6948                 return;
6949            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6950            /* [HGM] Kludge to handle engines that send FRC-style castling
6951               when they shouldn't (like TSCP-Gothic) */
6952            switch(moveType) {
6953              case WhiteASideCastleFR:
6954              case BlackASideCastleFR:
6955                toX+=2;
6956                currentMoveString[2]++;
6957                break;
6958              case WhiteHSideCastleFR:
6959              case BlackHSideCastleFR:
6960                toX--;
6961                currentMoveString[2]--;
6962                break;
6963              default: ; // nothing to do, but suppresses warning of pedantic compilers
6964            }
6965         }
6966         hintRequested = FALSE;
6967         lastHint[0] = NULLCHAR;
6968         bookRequested = FALSE;
6969         /* Program may be pondering now */
6970         cps->maybeThinking = TRUE;
6971         if (cps->sendTime == 2) cps->sendTime = 1;
6972         if (cps->offeredDraw) cps->offeredDraw--;
6973
6974         /* currentMoveString is set as a side-effect of ParseOneMove */
6975         strcpy(machineMove, currentMoveString);
6976         strcat(machineMove, "\n");
6977         strcpy(moveList[forwardMostMove], machineMove);
6978
6979         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6980
6981         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6982         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6983             int count = 0;
6984
6985             while( count < adjudicateLossPlies ) {
6986                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6987
6988                 if( count & 1 ) {
6989                     score = -score; /* Flip score for winning side */
6990                 }
6991
6992                 if( score > adjudicateLossThreshold ) {
6993                     break;
6994                 }
6995
6996                 count++;
6997             }
6998
6999             if( count >= adjudicateLossPlies ) {
7000                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7001
7002                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7003                     "Xboard adjudication", 
7004                     GE_XBOARD );
7005
7006                 return;
7007             }
7008         }
7009
7010         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7011
7012 #if ZIPPY
7013         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7014             first.initDone) {
7015           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7016                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7017                 SendToICS("draw ");
7018                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7019           }
7020           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7021           ics_user_moved = 1;
7022           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7023                 char buf[3*MSG_SIZ];
7024
7025                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7026                         programStats.score / 100.,
7027                         programStats.depth,
7028                         programStats.time / 100.,
7029                         (unsigned int)programStats.nodes,
7030                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7031                         programStats.movelist);
7032                 SendToICS(buf);
7033 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7034           }
7035         }
7036 #endif
7037
7038         /* [AS] Save move info and clear stats for next move */
7039         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7040         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7041         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7042         ClearProgramStats();
7043         thinkOutput[0] = NULLCHAR;
7044         hiddenThinkOutputState = 0;
7045
7046         bookHit = NULL;
7047         if (gameMode == TwoMachinesPlay) {
7048             /* [HGM] relaying draw offers moved to after reception of move */
7049             /* and interpreting offer as claim if it brings draw condition */
7050             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7051                 SendToProgram("draw\n", cps->other);
7052             }
7053             if (cps->other->sendTime) {
7054                 SendTimeRemaining(cps->other,
7055                                   cps->other->twoMachinesColor[0] == 'w');
7056             }
7057             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7058             if (firstMove && !bookHit) {
7059                 firstMove = FALSE;
7060                 if (cps->other->useColors) {
7061                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7062                 }
7063                 SendToProgram("go\n", cps->other);
7064             }
7065             cps->other->maybeThinking = TRUE;
7066         }
7067
7068         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7069         
7070         if (!pausing && appData.ringBellAfterMoves) {
7071             RingBell();
7072         }
7073
7074         /* 
7075          * Reenable menu items that were disabled while
7076          * machine was thinking
7077          */
7078         if (gameMode != TwoMachinesPlay)
7079             SetUserThinkingEnables();
7080
7081         // [HGM] book: after book hit opponent has received move and is now in force mode
7082         // force the book reply into it, and then fake that it outputted this move by jumping
7083         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7084         if(bookHit) {
7085                 static char bookMove[MSG_SIZ]; // a bit generous?
7086
7087                 strcpy(bookMove, "move ");
7088                 strcat(bookMove, bookHit);
7089                 message = bookMove;
7090                 cps = cps->other;
7091                 programStats.nodes = programStats.depth = programStats.time = 
7092                 programStats.score = programStats.got_only_move = 0;
7093                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7094
7095                 if(cps->lastPing != cps->lastPong) {
7096                     savedMessage = message; // args for deferred call
7097                     savedState = cps;
7098                     ScheduleDelayedEvent(DeferredBookMove, 10);
7099                     return;
7100                 }
7101                 goto FakeBookMove;
7102         }
7103
7104         return;
7105     }
7106
7107     /* Set special modes for chess engines.  Later something general
7108      *  could be added here; for now there is just one kludge feature,
7109      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7110      *  when "xboard" is given as an interactive command.
7111      */
7112     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7113         cps->useSigint = FALSE;
7114         cps->useSigterm = FALSE;
7115     }
7116     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7117       ParseFeatures(message+8, cps);
7118       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7119     }
7120
7121     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7122      * want this, I was asked to put it in, and obliged.
7123      */
7124     if (!strncmp(message, "setboard ", 9)) {
7125         Board initial_position;
7126
7127         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7128
7129         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7130             DisplayError(_("Bad FEN received from engine"), 0);
7131             return ;
7132         } else {
7133            Reset(TRUE, FALSE);
7134            CopyBoard(boards[0], initial_position);
7135            initialRulePlies = FENrulePlies;
7136            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7137            else gameMode = MachinePlaysBlack;                 
7138            DrawPosition(FALSE, boards[currentMove]);
7139         }
7140         return;
7141     }
7142
7143     /*
7144      * Look for communication commands
7145      */
7146     if (!strncmp(message, "telluser ", 9)) {
7147         DisplayNote(message + 9);
7148         return;
7149     }
7150     if (!strncmp(message, "tellusererror ", 14)) {
7151         cps->userError = 1;
7152         DisplayError(message + 14, 0);
7153         return;
7154     }
7155     if (!strncmp(message, "tellopponent ", 13)) {
7156       if (appData.icsActive) {
7157         if (loggedOn) {
7158           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7159           SendToICS(buf1);
7160         }
7161       } else {
7162         DisplayNote(message + 13);
7163       }
7164       return;
7165     }
7166     if (!strncmp(message, "tellothers ", 11)) {
7167       if (appData.icsActive) {
7168         if (loggedOn) {
7169           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7170           SendToICS(buf1);
7171         }
7172       }
7173       return;
7174     }
7175     if (!strncmp(message, "tellall ", 8)) {
7176       if (appData.icsActive) {
7177         if (loggedOn) {
7178           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7179           SendToICS(buf1);
7180         }
7181       } else {
7182         DisplayNote(message + 8);
7183       }
7184       return;
7185     }
7186     if (strncmp(message, "warning", 7) == 0) {
7187         /* Undocumented feature, use tellusererror in new code */
7188         DisplayError(message, 0);
7189         return;
7190     }
7191     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7192         strcpy(realname, cps->tidy);
7193         strcat(realname, " query");
7194         AskQuestion(realname, buf2, buf1, cps->pr);
7195         return;
7196     }
7197     /* Commands from the engine directly to ICS.  We don't allow these to be 
7198      *  sent until we are logged on. Crafty kibitzes have been known to 
7199      *  interfere with the login process.
7200      */
7201     if (loggedOn) {
7202         if (!strncmp(message, "tellics ", 8)) {
7203             SendToICS(message + 8);
7204             SendToICS("\n");
7205             return;
7206         }
7207         if (!strncmp(message, "tellicsnoalias ", 15)) {
7208             SendToICS(ics_prefix);
7209             SendToICS(message + 15);
7210             SendToICS("\n");
7211             return;
7212         }
7213         /* The following are for backward compatibility only */
7214         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7215             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7216             SendToICS(ics_prefix);
7217             SendToICS(message);
7218             SendToICS("\n");
7219             return;
7220         }
7221     }
7222     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7223         return;
7224     }
7225     /*
7226      * If the move is illegal, cancel it and redraw the board.
7227      * Also deal with other error cases.  Matching is rather loose
7228      * here to accommodate engines written before the spec.
7229      */
7230     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7231         strncmp(message, "Error", 5) == 0) {
7232         if (StrStr(message, "name") || 
7233             StrStr(message, "rating") || StrStr(message, "?") ||
7234             StrStr(message, "result") || StrStr(message, "board") ||
7235             StrStr(message, "bk") || StrStr(message, "computer") ||
7236             StrStr(message, "variant") || StrStr(message, "hint") ||
7237             StrStr(message, "random") || StrStr(message, "depth") ||
7238             StrStr(message, "accepted")) {
7239             return;
7240         }
7241         if (StrStr(message, "protover")) {
7242           /* Program is responding to input, so it's apparently done
7243              initializing, and this error message indicates it is
7244              protocol version 1.  So we don't need to wait any longer
7245              for it to initialize and send feature commands. */
7246           FeatureDone(cps, 1);
7247           cps->protocolVersion = 1;
7248           return;
7249         }
7250         cps->maybeThinking = FALSE;
7251
7252         if (StrStr(message, "draw")) {
7253             /* Program doesn't have "draw" command */
7254             cps->sendDrawOffers = 0;
7255             return;
7256         }
7257         if (cps->sendTime != 1 &&
7258             (StrStr(message, "time") || StrStr(message, "otim"))) {
7259           /* Program apparently doesn't have "time" or "otim" command */
7260           cps->sendTime = 0;
7261           return;
7262         }
7263         if (StrStr(message, "analyze")) {
7264             cps->analysisSupport = FALSE;
7265             cps->analyzing = FALSE;
7266             Reset(FALSE, TRUE);
7267             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7268             DisplayError(buf2, 0);
7269             return;
7270         }
7271         if (StrStr(message, "(no matching move)st")) {
7272           /* Special kludge for GNU Chess 4 only */
7273           cps->stKludge = TRUE;
7274           SendTimeControl(cps, movesPerSession, timeControl,
7275                           timeIncrement, appData.searchDepth,
7276                           searchTime);
7277           return;
7278         }
7279         if (StrStr(message, "(no matching move)sd")) {
7280           /* Special kludge for GNU Chess 4 only */
7281           cps->sdKludge = TRUE;
7282           SendTimeControl(cps, movesPerSession, timeControl,
7283                           timeIncrement, appData.searchDepth,
7284                           searchTime);
7285           return;
7286         }
7287         if (!StrStr(message, "llegal")) {
7288             return;
7289         }
7290         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7291             gameMode == IcsIdle) return;
7292         if (forwardMostMove <= backwardMostMove) return;
7293         if (pausing) PauseEvent();
7294       if(appData.forceIllegal) {
7295             // [HGM] illegal: machine refused move; force position after move into it
7296           SendToProgram("force\n", cps);
7297           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7298                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7299                 // when black is to move, while there might be nothing on a2 or black
7300                 // might already have the move. So send the board as if white has the move.
7301                 // But first we must change the stm of the engine, as it refused the last move
7302                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7303                 if(WhiteOnMove(forwardMostMove)) {
7304                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7305                     SendBoard(cps, forwardMostMove); // kludgeless board
7306                 } else {
7307                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7308                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7309                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7310                 }
7311           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7312             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7313                  gameMode == TwoMachinesPlay)
7314               SendToProgram("go\n", cps);
7315             return;
7316       } else
7317         if (gameMode == PlayFromGameFile) {
7318             /* Stop reading this game file */
7319             gameMode = EditGame;
7320             ModeHighlight();
7321         }
7322         currentMove = forwardMostMove-1;
7323         DisplayMove(currentMove-1); /* before DisplayMoveError */
7324         SwitchClocks(forwardMostMove-1); // [HGM] race
7325         DisplayBothClocks();
7326         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7327                 parseList[currentMove], cps->which);
7328         DisplayMoveError(buf1);
7329         DrawPosition(FALSE, boards[currentMove]);
7330
7331         /* [HGM] illegal-move claim should forfeit game when Xboard */
7332         /* only passes fully legal moves                            */
7333         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7334             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7335                                 "False illegal-move claim", GE_XBOARD );
7336         }
7337         return;
7338     }
7339     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7340         /* Program has a broken "time" command that
7341            outputs a string not ending in newline.
7342            Don't use it. */
7343         cps->sendTime = 0;
7344     }
7345     
7346     /*
7347      * If chess program startup fails, exit with an error message.
7348      * Attempts to recover here are futile.
7349      */
7350     if ((StrStr(message, "unknown host") != NULL)
7351         || (StrStr(message, "No remote directory") != NULL)
7352         || (StrStr(message, "not found") != NULL)
7353         || (StrStr(message, "No such file") != NULL)
7354         || (StrStr(message, "can't alloc") != NULL)
7355         || (StrStr(message, "Permission denied") != NULL)) {
7356
7357         cps->maybeThinking = FALSE;
7358         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7359                 cps->which, cps->program, cps->host, message);
7360         RemoveInputSource(cps->isr);
7361         DisplayFatalError(buf1, 0, 1);
7362         return;
7363     }
7364     
7365     /* 
7366      * Look for hint output
7367      */
7368     if (sscanf(message, "Hint: %s", buf1) == 1) {
7369         if (cps == &first && hintRequested) {
7370             hintRequested = FALSE;
7371             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7372                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7373                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7374                                     PosFlags(forwardMostMove),
7375                                     fromY, fromX, toY, toX, promoChar, buf1);
7376                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7377                 DisplayInformation(buf2);
7378             } else {
7379                 /* Hint move could not be parsed!? */
7380               snprintf(buf2, sizeof(buf2),
7381                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7382                         buf1, cps->which);
7383                 DisplayError(buf2, 0);
7384             }
7385         } else {
7386             strcpy(lastHint, buf1);
7387         }
7388         return;
7389     }
7390
7391     /*
7392      * Ignore other messages if game is not in progress
7393      */
7394     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7395         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7396
7397     /*
7398      * look for win, lose, draw, or draw offer
7399      */
7400     if (strncmp(message, "1-0", 3) == 0) {
7401         char *p, *q, *r = "";
7402         p = strchr(message, '{');
7403         if (p) {
7404             q = strchr(p, '}');
7405             if (q) {
7406                 *q = NULLCHAR;
7407                 r = p + 1;
7408             }
7409         }
7410         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7411         return;
7412     } else if (strncmp(message, "0-1", 3) == 0) {
7413         char *p, *q, *r = "";
7414         p = strchr(message, '{');
7415         if (p) {
7416             q = strchr(p, '}');
7417             if (q) {
7418                 *q = NULLCHAR;
7419                 r = p + 1;
7420             }
7421         }
7422         /* Kludge for Arasan 4.1 bug */
7423         if (strcmp(r, "Black resigns") == 0) {
7424             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7425             return;
7426         }
7427         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7428         return;
7429     } else if (strncmp(message, "1/2", 3) == 0) {
7430         char *p, *q, *r = "";
7431         p = strchr(message, '{');
7432         if (p) {
7433             q = strchr(p, '}');
7434             if (q) {
7435                 *q = NULLCHAR;
7436                 r = p + 1;
7437             }
7438         }
7439             
7440         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7441         return;
7442
7443     } else if (strncmp(message, "White resign", 12) == 0) {
7444         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7445         return;
7446     } else if (strncmp(message, "Black resign", 12) == 0) {
7447         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7448         return;
7449     } else if (strncmp(message, "White matches", 13) == 0 ||
7450                strncmp(message, "Black matches", 13) == 0   ) {
7451         /* [HGM] ignore GNUShogi noises */
7452         return;
7453     } else if (strncmp(message, "White", 5) == 0 &&
7454                message[5] != '(' &&
7455                StrStr(message, "Black") == NULL) {
7456         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7457         return;
7458     } else if (strncmp(message, "Black", 5) == 0 &&
7459                message[5] != '(') {
7460         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7461         return;
7462     } else if (strcmp(message, "resign") == 0 ||
7463                strcmp(message, "computer resigns") == 0) {
7464         switch (gameMode) {
7465           case MachinePlaysBlack:
7466           case IcsPlayingBlack:
7467             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7468             break;
7469           case MachinePlaysWhite:
7470           case IcsPlayingWhite:
7471             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7472             break;
7473           case TwoMachinesPlay:
7474             if (cps->twoMachinesColor[0] == 'w')
7475               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7476             else
7477               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7478             break;
7479           default:
7480             /* can't happen */
7481             break;
7482         }
7483         return;
7484     } else if (strncmp(message, "opponent mates", 14) == 0) {
7485         switch (gameMode) {
7486           case MachinePlaysBlack:
7487           case IcsPlayingBlack:
7488             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7489             break;
7490           case MachinePlaysWhite:
7491           case IcsPlayingWhite:
7492             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7493             break;
7494           case TwoMachinesPlay:
7495             if (cps->twoMachinesColor[0] == 'w')
7496               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7497             else
7498               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7499             break;
7500           default:
7501             /* can't happen */
7502             break;
7503         }
7504         return;
7505     } else if (strncmp(message, "computer mates", 14) == 0) {
7506         switch (gameMode) {
7507           case MachinePlaysBlack:
7508           case IcsPlayingBlack:
7509             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7510             break;
7511           case MachinePlaysWhite:
7512           case IcsPlayingWhite:
7513             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7514             break;
7515           case TwoMachinesPlay:
7516             if (cps->twoMachinesColor[0] == 'w')
7517               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7518             else
7519               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7520             break;
7521           default:
7522             /* can't happen */
7523             break;
7524         }
7525         return;
7526     } else if (strncmp(message, "checkmate", 9) == 0) {
7527         if (WhiteOnMove(forwardMostMove)) {
7528             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7529         } else {
7530             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7531         }
7532         return;
7533     } else if (strstr(message, "Draw") != NULL ||
7534                strstr(message, "game is a draw") != NULL) {
7535         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7536         return;
7537     } else if (strstr(message, "offer") != NULL &&
7538                strstr(message, "draw") != NULL) {
7539 #if ZIPPY
7540         if (appData.zippyPlay && first.initDone) {
7541             /* Relay offer to ICS */
7542             SendToICS(ics_prefix);
7543             SendToICS("draw\n");
7544         }
7545 #endif
7546         cps->offeredDraw = 2; /* valid until this engine moves twice */
7547         if (gameMode == TwoMachinesPlay) {
7548             if (cps->other->offeredDraw) {
7549                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7550             /* [HGM] in two-machine mode we delay relaying draw offer      */
7551             /* until after we also have move, to see if it is really claim */
7552             }
7553         } else if (gameMode == MachinePlaysWhite ||
7554                    gameMode == MachinePlaysBlack) {
7555           if (userOfferedDraw) {
7556             DisplayInformation(_("Machine accepts your draw offer"));
7557             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7558           } else {
7559             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7560           }
7561         }
7562     }
7563
7564     
7565     /*
7566      * Look for thinking output
7567      */
7568     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7569           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7570                                 ) {
7571         int plylev, mvleft, mvtot, curscore, time;
7572         char mvname[MOVE_LEN];
7573         u64 nodes; // [DM]
7574         char plyext;
7575         int ignore = FALSE;
7576         int prefixHint = FALSE;
7577         mvname[0] = NULLCHAR;
7578
7579         switch (gameMode) {
7580           case MachinePlaysBlack:
7581           case IcsPlayingBlack:
7582             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7583             break;
7584           case MachinePlaysWhite:
7585           case IcsPlayingWhite:
7586             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7587             break;
7588           case AnalyzeMode:
7589           case AnalyzeFile:
7590             break;
7591           case IcsObserving: /* [DM] icsEngineAnalyze */
7592             if (!appData.icsEngineAnalyze) ignore = TRUE;
7593             break;
7594           case TwoMachinesPlay:
7595             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7596                 ignore = TRUE;
7597             }
7598             break;
7599           default:
7600             ignore = TRUE;
7601             break;
7602         }
7603
7604         if (!ignore) {
7605             buf1[0] = NULLCHAR;
7606             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7607                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7608
7609                 if (plyext != ' ' && plyext != '\t') {
7610                     time *= 100;
7611                 }
7612
7613                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7614                 if( cps->scoreIsAbsolute && 
7615                     ( gameMode == MachinePlaysBlack ||
7616                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7617                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7618                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7619                      !WhiteOnMove(currentMove)
7620                     ) )
7621                 {
7622                     curscore = -curscore;
7623                 }
7624
7625
7626                 programStats.depth = plylev;
7627                 programStats.nodes = nodes;
7628                 programStats.time = time;
7629                 programStats.score = curscore;
7630                 programStats.got_only_move = 0;
7631
7632                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7633                         int ticklen;
7634
7635                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7636                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7637                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7638                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7639                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7640                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7641                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7642                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7643                 }
7644
7645                 /* Buffer overflow protection */
7646                 if (buf1[0] != NULLCHAR) {
7647                     if (strlen(buf1) >= sizeof(programStats.movelist)
7648                         && appData.debugMode) {
7649                         fprintf(debugFP,
7650                                 "PV is too long; using the first %u bytes.\n",
7651                                 (unsigned) sizeof(programStats.movelist) - 1);
7652                     }
7653
7654                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7655                 } else {
7656                     sprintf(programStats.movelist, " no PV\n");
7657                 }
7658
7659                 if (programStats.seen_stat) {
7660                     programStats.ok_to_send = 1;
7661                 }
7662
7663                 if (strchr(programStats.movelist, '(') != NULL) {
7664                     programStats.line_is_book = 1;
7665                     programStats.nr_moves = 0;
7666                     programStats.moves_left = 0;
7667                 } else {
7668                     programStats.line_is_book = 0;
7669                 }
7670
7671                 SendProgramStatsToFrontend( cps, &programStats );
7672
7673                 /* 
7674                     [AS] Protect the thinkOutput buffer from overflow... this
7675                     is only useful if buf1 hasn't overflowed first!
7676                 */
7677                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7678                         plylev, 
7679                         (gameMode == TwoMachinesPlay ?
7680                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7681                         ((double) curscore) / 100.0,
7682                         prefixHint ? lastHint : "",
7683                         prefixHint ? " " : "" );
7684
7685                 if( buf1[0] != NULLCHAR ) {
7686                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7687
7688                     if( strlen(buf1) > max_len ) {
7689                         if( appData.debugMode) {
7690                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7691                         }
7692                         buf1[max_len+1] = '\0';
7693                     }
7694
7695                     strcat( thinkOutput, buf1 );
7696                 }
7697
7698                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7699                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7700                     DisplayMove(currentMove - 1);
7701                 }
7702                 return;
7703
7704             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7705                 /* crafty (9.25+) says "(only move) <move>"
7706                  * if there is only 1 legal move
7707                  */
7708                 sscanf(p, "(only move) %s", buf1);
7709                 sprintf(thinkOutput, "%s (only move)", buf1);
7710                 sprintf(programStats.movelist, "%s (only move)", buf1);
7711                 programStats.depth = 1;
7712                 programStats.nr_moves = 1;
7713                 programStats.moves_left = 1;
7714                 programStats.nodes = 1;
7715                 programStats.time = 1;
7716                 programStats.got_only_move = 1;
7717
7718                 /* Not really, but we also use this member to
7719                    mean "line isn't going to change" (Crafty
7720                    isn't searching, so stats won't change) */
7721                 programStats.line_is_book = 1;
7722
7723                 SendProgramStatsToFrontend( cps, &programStats );
7724                 
7725                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7726                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7727                     DisplayMove(currentMove - 1);
7728                 }
7729                 return;
7730             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7731                               &time, &nodes, &plylev, &mvleft,
7732                               &mvtot, mvname) >= 5) {
7733                 /* The stat01: line is from Crafty (9.29+) in response
7734                    to the "." command */
7735                 programStats.seen_stat = 1;
7736                 cps->maybeThinking = TRUE;
7737
7738                 if (programStats.got_only_move || !appData.periodicUpdates)
7739                   return;
7740
7741                 programStats.depth = plylev;
7742                 programStats.time = time;
7743                 programStats.nodes = nodes;
7744                 programStats.moves_left = mvleft;
7745                 programStats.nr_moves = mvtot;
7746                 strcpy(programStats.move_name, mvname);
7747                 programStats.ok_to_send = 1;
7748                 programStats.movelist[0] = '\0';
7749
7750                 SendProgramStatsToFrontend( cps, &programStats );
7751
7752                 return;
7753
7754             } else if (strncmp(message,"++",2) == 0) {
7755                 /* Crafty 9.29+ outputs this */
7756                 programStats.got_fail = 2;
7757                 return;
7758
7759             } else if (strncmp(message,"--",2) == 0) {
7760                 /* Crafty 9.29+ outputs this */
7761                 programStats.got_fail = 1;
7762                 return;
7763
7764             } else if (thinkOutput[0] != NULLCHAR &&
7765                        strncmp(message, "    ", 4) == 0) {
7766                 unsigned message_len;
7767
7768                 p = message;
7769                 while (*p && *p == ' ') p++;
7770
7771                 message_len = strlen( p );
7772
7773                 /* [AS] Avoid buffer overflow */
7774                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7775                     strcat(thinkOutput, " ");
7776                     strcat(thinkOutput, p);
7777                 }
7778
7779                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7780                     strcat(programStats.movelist, " ");
7781                     strcat(programStats.movelist, p);
7782                 }
7783
7784                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7785                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7786                     DisplayMove(currentMove - 1);
7787                 }
7788                 return;
7789             }
7790         }
7791         else {
7792             buf1[0] = NULLCHAR;
7793
7794             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7795                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7796             {
7797                 ChessProgramStats cpstats;
7798
7799                 if (plyext != ' ' && plyext != '\t') {
7800                     time *= 100;
7801                 }
7802
7803                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7804                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7805                     curscore = -curscore;
7806                 }
7807
7808                 cpstats.depth = plylev;
7809                 cpstats.nodes = nodes;
7810                 cpstats.time = time;
7811                 cpstats.score = curscore;
7812                 cpstats.got_only_move = 0;
7813                 cpstats.movelist[0] = '\0';
7814
7815                 if (buf1[0] != NULLCHAR) {
7816                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7817                 }
7818
7819                 cpstats.ok_to_send = 0;
7820                 cpstats.line_is_book = 0;
7821                 cpstats.nr_moves = 0;
7822                 cpstats.moves_left = 0;
7823
7824                 SendProgramStatsToFrontend( cps, &cpstats );
7825             }
7826         }
7827     }
7828 }
7829
7830
7831 /* Parse a game score from the character string "game", and
7832    record it as the history of the current game.  The game
7833    score is NOT assumed to start from the standard position. 
7834    The display is not updated in any way.
7835    */
7836 void
7837 ParseGameHistory(game)
7838      char *game;
7839 {
7840     ChessMove moveType;
7841     int fromX, fromY, toX, toY, boardIndex;
7842     char promoChar;
7843     char *p, *q;
7844     char buf[MSG_SIZ];
7845
7846     if (appData.debugMode)
7847       fprintf(debugFP, "Parsing game history: %s\n", game);
7848
7849     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7850     gameInfo.site = StrSave(appData.icsHost);
7851     gameInfo.date = PGNDate();
7852     gameInfo.round = StrSave("-");
7853
7854     /* Parse out names of players */
7855     while (*game == ' ') game++;
7856     p = buf;
7857     while (*game != ' ') *p++ = *game++;
7858     *p = NULLCHAR;
7859     gameInfo.white = StrSave(buf);
7860     while (*game == ' ') game++;
7861     p = buf;
7862     while (*game != ' ' && *game != '\n') *p++ = *game++;
7863     *p = NULLCHAR;
7864     gameInfo.black = StrSave(buf);
7865
7866     /* Parse moves */
7867     boardIndex = blackPlaysFirst ? 1 : 0;
7868     yynewstr(game);
7869     for (;;) {
7870         yyboardindex = boardIndex;
7871         moveType = (ChessMove) yylex();
7872         switch (moveType) {
7873           case IllegalMove:             /* maybe suicide chess, etc. */
7874   if (appData.debugMode) {
7875     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7876     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7877     setbuf(debugFP, NULL);
7878   }
7879           case WhitePromotionChancellor:
7880           case BlackPromotionChancellor:
7881           case WhitePromotionArchbishop:
7882           case BlackPromotionArchbishop:
7883           case WhitePromotionQueen:
7884           case BlackPromotionQueen:
7885           case WhitePromotionRook:
7886           case BlackPromotionRook:
7887           case WhitePromotionBishop:
7888           case BlackPromotionBishop:
7889           case WhitePromotionKnight:
7890           case BlackPromotionKnight:
7891           case WhitePromotionKing:
7892           case BlackPromotionKing:
7893           case NormalMove:
7894           case WhiteCapturesEnPassant:
7895           case BlackCapturesEnPassant:
7896           case WhiteKingSideCastle:
7897           case WhiteQueenSideCastle:
7898           case BlackKingSideCastle:
7899           case BlackQueenSideCastle:
7900           case WhiteKingSideCastleWild:
7901           case WhiteQueenSideCastleWild:
7902           case BlackKingSideCastleWild:
7903           case BlackQueenSideCastleWild:
7904           /* PUSH Fabien */
7905           case WhiteHSideCastleFR:
7906           case WhiteASideCastleFR:
7907           case BlackHSideCastleFR:
7908           case BlackASideCastleFR:
7909           /* POP Fabien */
7910             fromX = currentMoveString[0] - AAA;
7911             fromY = currentMoveString[1] - ONE;
7912             toX = currentMoveString[2] - AAA;
7913             toY = currentMoveString[3] - ONE;
7914             promoChar = currentMoveString[4];
7915             break;
7916           case WhiteDrop:
7917           case BlackDrop:
7918             fromX = moveType == WhiteDrop ?
7919               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7920             (int) CharToPiece(ToLower(currentMoveString[0]));
7921             fromY = DROP_RANK;
7922             toX = currentMoveString[2] - AAA;
7923             toY = currentMoveString[3] - ONE;
7924             promoChar = NULLCHAR;
7925             break;
7926           case AmbiguousMove:
7927             /* bug? */
7928             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7929   if (appData.debugMode) {
7930     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7931     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7932     setbuf(debugFP, NULL);
7933   }
7934             DisplayError(buf, 0);
7935             return;
7936           case ImpossibleMove:
7937             /* bug? */
7938             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7939   if (appData.debugMode) {
7940     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7941     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7942     setbuf(debugFP, NULL);
7943   }
7944             DisplayError(buf, 0);
7945             return;
7946           case (ChessMove) 0:   /* end of file */
7947             if (boardIndex < backwardMostMove) {
7948                 /* Oops, gap.  How did that happen? */
7949                 DisplayError(_("Gap in move list"), 0);
7950                 return;
7951             }
7952             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7953             if (boardIndex > forwardMostMove) {
7954                 forwardMostMove = boardIndex;
7955             }
7956             return;
7957           case ElapsedTime:
7958             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7959                 strcat(parseList[boardIndex-1], " ");
7960                 strcat(parseList[boardIndex-1], yy_text);
7961             }
7962             continue;
7963           case Comment:
7964           case PGNTag:
7965           case NAG:
7966           default:
7967             /* ignore */
7968             continue;
7969           case WhiteWins:
7970           case BlackWins:
7971           case GameIsDrawn:
7972           case GameUnfinished:
7973             if (gameMode == IcsExamining) {
7974                 if (boardIndex < backwardMostMove) {
7975                     /* Oops, gap.  How did that happen? */
7976                     return;
7977                 }
7978                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7979                 return;
7980             }
7981             gameInfo.result = moveType;
7982             p = strchr(yy_text, '{');
7983             if (p == NULL) p = strchr(yy_text, '(');
7984             if (p == NULL) {
7985                 p = yy_text;
7986                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7987             } else {
7988                 q = strchr(p, *p == '{' ? '}' : ')');
7989                 if (q != NULL) *q = NULLCHAR;
7990                 p++;
7991             }
7992             gameInfo.resultDetails = StrSave(p);
7993             continue;
7994         }
7995         if (boardIndex >= forwardMostMove &&
7996             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7997             backwardMostMove = blackPlaysFirst ? 1 : 0;
7998             return;
7999         }
8000         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8001                                  fromY, fromX, toY, toX, promoChar,
8002                                  parseList[boardIndex]);
8003         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8004         /* currentMoveString is set as a side-effect of yylex */
8005         strcpy(moveList[boardIndex], currentMoveString);
8006         strcat(moveList[boardIndex], "\n");
8007         boardIndex++;
8008         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8009         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8010           case MT_NONE:
8011           case MT_STALEMATE:
8012           default:
8013             break;
8014           case MT_CHECK:
8015             if(gameInfo.variant != VariantShogi)
8016                 strcat(parseList[boardIndex - 1], "+");
8017             break;
8018           case MT_CHECKMATE:
8019           case MT_STAINMATE:
8020             strcat(parseList[boardIndex - 1], "#");
8021             break;
8022         }
8023     }
8024 }
8025
8026
8027 /* Apply a move to the given board  */
8028 void
8029 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8030      int fromX, fromY, toX, toY;
8031      int promoChar;
8032      Board board;
8033 {
8034   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8035   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8036
8037     /* [HGM] compute & store e.p. status and castling rights for new position */
8038     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8039     { int i;
8040
8041       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8042       oldEP = (signed char)board[EP_STATUS];
8043       board[EP_STATUS] = EP_NONE;
8044
8045       if( board[toY][toX] != EmptySquare ) 
8046            board[EP_STATUS] = EP_CAPTURE;  
8047
8048       if( board[fromY][fromX] == WhitePawn ) {
8049            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8050                board[EP_STATUS] = EP_PAWN_MOVE;
8051            if( toY-fromY==2) {
8052                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8053                         gameInfo.variant != VariantBerolina || toX < fromX)
8054                       board[EP_STATUS] = toX | berolina;
8055                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8056                         gameInfo.variant != VariantBerolina || toX > fromX) 
8057                       board[EP_STATUS] = toX;
8058            }
8059       } else 
8060       if( board[fromY][fromX] == BlackPawn ) {
8061            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8062                board[EP_STATUS] = EP_PAWN_MOVE; 
8063            if( toY-fromY== -2) {
8064                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8065                         gameInfo.variant != VariantBerolina || toX < fromX)
8066                       board[EP_STATUS] = toX | berolina;
8067                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8068                         gameInfo.variant != VariantBerolina || toX > fromX) 
8069                       board[EP_STATUS] = toX;
8070            }
8071        }
8072
8073        for(i=0; i<nrCastlingRights; i++) {
8074            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8075               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8076              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8077        }
8078
8079     }
8080
8081   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8082   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8083        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8084          
8085   if (fromX == toX && fromY == toY) return;
8086
8087   if (fromY == DROP_RANK) {
8088         /* must be first */
8089         piece = board[toY][toX] = (ChessSquare) fromX;
8090   } else {
8091      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8092      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8093      if(gameInfo.variant == VariantKnightmate)
8094          king += (int) WhiteUnicorn - (int) WhiteKing;
8095
8096     /* Code added by Tord: */
8097     /* FRC castling assumed when king captures friendly rook. */
8098     if (board[fromY][fromX] == WhiteKing &&
8099              board[toY][toX] == WhiteRook) {
8100       board[fromY][fromX] = EmptySquare;
8101       board[toY][toX] = EmptySquare;
8102       if(toX > fromX) {
8103         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8104       } else {
8105         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8106       }
8107     } else if (board[fromY][fromX] == BlackKing &&
8108                board[toY][toX] == BlackRook) {
8109       board[fromY][fromX] = EmptySquare;
8110       board[toY][toX] = EmptySquare;
8111       if(toX > fromX) {
8112         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8113       } else {
8114         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8115       }
8116     /* End of code added by Tord */
8117
8118     } else if (board[fromY][fromX] == king
8119         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8120         && toY == fromY && toX > fromX+1) {
8121         board[fromY][fromX] = EmptySquare;
8122         board[toY][toX] = king;
8123         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8124         board[fromY][BOARD_RGHT-1] = EmptySquare;
8125     } else if (board[fromY][fromX] == king
8126         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8127                && toY == fromY && toX < fromX-1) {
8128         board[fromY][fromX] = EmptySquare;
8129         board[toY][toX] = king;
8130         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8131         board[fromY][BOARD_LEFT] = EmptySquare;
8132     } else if (board[fromY][fromX] == WhitePawn
8133                && toY >= BOARD_HEIGHT-promoRank
8134                && gameInfo.variant != VariantXiangqi
8135                ) {
8136         /* white pawn promotion */
8137         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8138         if (board[toY][toX] == EmptySquare) {
8139             board[toY][toX] = WhiteQueen;
8140         }
8141         if(gameInfo.variant==VariantBughouse ||
8142            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8143             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8144         board[fromY][fromX] = EmptySquare;
8145     } else if ((fromY == BOARD_HEIGHT-4)
8146                && (toX != fromX)
8147                && gameInfo.variant != VariantXiangqi
8148                && gameInfo.variant != VariantBerolina
8149                && (board[fromY][fromX] == WhitePawn)
8150                && (board[toY][toX] == EmptySquare)) {
8151         board[fromY][fromX] = EmptySquare;
8152         board[toY][toX] = WhitePawn;
8153         captured = board[toY - 1][toX];
8154         board[toY - 1][toX] = EmptySquare;
8155     } else if ((fromY == BOARD_HEIGHT-4)
8156                && (toX == fromX)
8157                && gameInfo.variant == VariantBerolina
8158                && (board[fromY][fromX] == WhitePawn)
8159                && (board[toY][toX] == EmptySquare)) {
8160         board[fromY][fromX] = EmptySquare;
8161         board[toY][toX] = WhitePawn;
8162         if(oldEP & EP_BEROLIN_A) {
8163                 captured = board[fromY][fromX-1];
8164                 board[fromY][fromX-1] = EmptySquare;
8165         }else{  captured = board[fromY][fromX+1];
8166                 board[fromY][fromX+1] = EmptySquare;
8167         }
8168     } else if (board[fromY][fromX] == king
8169         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8170                && toY == fromY && toX > fromX+1) {
8171         board[fromY][fromX] = EmptySquare;
8172         board[toY][toX] = king;
8173         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8174         board[fromY][BOARD_RGHT-1] = EmptySquare;
8175     } else if (board[fromY][fromX] == king
8176         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8177                && toY == fromY && toX < fromX-1) {
8178         board[fromY][fromX] = EmptySquare;
8179         board[toY][toX] = king;
8180         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8181         board[fromY][BOARD_LEFT] = EmptySquare;
8182     } else if (fromY == 7 && fromX == 3
8183                && board[fromY][fromX] == BlackKing
8184                && toY == 7 && toX == 5) {
8185         board[fromY][fromX] = EmptySquare;
8186         board[toY][toX] = BlackKing;
8187         board[fromY][7] = EmptySquare;
8188         board[toY][4] = BlackRook;
8189     } else if (fromY == 7 && fromX == 3
8190                && board[fromY][fromX] == BlackKing
8191                && toY == 7 && toX == 1) {
8192         board[fromY][fromX] = EmptySquare;
8193         board[toY][toX] = BlackKing;
8194         board[fromY][0] = EmptySquare;
8195         board[toY][2] = BlackRook;
8196     } else if (board[fromY][fromX] == BlackPawn
8197                && toY < promoRank
8198                && gameInfo.variant != VariantXiangqi
8199                ) {
8200         /* black pawn promotion */
8201         board[toY][toX] = CharToPiece(ToLower(promoChar));
8202         if (board[toY][toX] == EmptySquare) {
8203             board[toY][toX] = BlackQueen;
8204         }
8205         if(gameInfo.variant==VariantBughouse ||
8206            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8207             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8208         board[fromY][fromX] = EmptySquare;
8209     } else if ((fromY == 3)
8210                && (toX != fromX)
8211                && gameInfo.variant != VariantXiangqi
8212                && gameInfo.variant != VariantBerolina
8213                && (board[fromY][fromX] == BlackPawn)
8214                && (board[toY][toX] == EmptySquare)) {
8215         board[fromY][fromX] = EmptySquare;
8216         board[toY][toX] = BlackPawn;
8217         captured = board[toY + 1][toX];
8218         board[toY + 1][toX] = EmptySquare;
8219     } else if ((fromY == 3)
8220                && (toX == fromX)
8221                && gameInfo.variant == VariantBerolina
8222                && (board[fromY][fromX] == BlackPawn)
8223                && (board[toY][toX] == EmptySquare)) {
8224         board[fromY][fromX] = EmptySquare;
8225         board[toY][toX] = BlackPawn;
8226         if(oldEP & EP_BEROLIN_A) {
8227                 captured = board[fromY][fromX-1];
8228                 board[fromY][fromX-1] = EmptySquare;
8229         }else{  captured = board[fromY][fromX+1];
8230                 board[fromY][fromX+1] = EmptySquare;
8231         }
8232     } else {
8233         board[toY][toX] = board[fromY][fromX];
8234         board[fromY][fromX] = EmptySquare;
8235     }
8236
8237     /* [HGM] now we promote for Shogi, if needed */
8238     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8239         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8240   }
8241
8242     if (gameInfo.holdingsWidth != 0) {
8243
8244       /* !!A lot more code needs to be written to support holdings  */
8245       /* [HGM] OK, so I have written it. Holdings are stored in the */
8246       /* penultimate board files, so they are automaticlly stored   */
8247       /* in the game history.                                       */
8248       if (fromY == DROP_RANK) {
8249         /* Delete from holdings, by decreasing count */
8250         /* and erasing image if necessary            */
8251         p = (int) fromX;
8252         if(p < (int) BlackPawn) { /* white drop */
8253              p -= (int)WhitePawn;
8254                  p = PieceToNumber((ChessSquare)p);
8255              if(p >= gameInfo.holdingsSize) p = 0;
8256              if(--board[p][BOARD_WIDTH-2] <= 0)
8257                   board[p][BOARD_WIDTH-1] = EmptySquare;
8258              if((int)board[p][BOARD_WIDTH-2] < 0)
8259                         board[p][BOARD_WIDTH-2] = 0;
8260         } else {                  /* black drop */
8261              p -= (int)BlackPawn;
8262                  p = PieceToNumber((ChessSquare)p);
8263              if(p >= gameInfo.holdingsSize) p = 0;
8264              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8265                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8266              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8267                         board[BOARD_HEIGHT-1-p][1] = 0;
8268         }
8269       }
8270       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8271           && gameInfo.variant != VariantBughouse        ) {
8272         /* [HGM] holdings: Add to holdings, if holdings exist */
8273         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8274                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8275                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8276         }
8277         p = (int) captured;
8278         if (p >= (int) BlackPawn) {
8279           p -= (int)BlackPawn;
8280           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8281                   /* in Shogi restore piece to its original  first */
8282                   captured = (ChessSquare) (DEMOTED captured);
8283                   p = DEMOTED p;
8284           }
8285           p = PieceToNumber((ChessSquare)p);
8286           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8287           board[p][BOARD_WIDTH-2]++;
8288           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8289         } else {
8290           p -= (int)WhitePawn;
8291           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8292                   captured = (ChessSquare) (DEMOTED captured);
8293                   p = DEMOTED p;
8294           }
8295           p = PieceToNumber((ChessSquare)p);
8296           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8297           board[BOARD_HEIGHT-1-p][1]++;
8298           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8299         }
8300       }
8301     } else if (gameInfo.variant == VariantAtomic) {
8302       if (captured != EmptySquare) {
8303         int y, x;
8304         for (y = toY-1; y <= toY+1; y++) {
8305           for (x = toX-1; x <= toX+1; x++) {
8306             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8307                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8308               board[y][x] = EmptySquare;
8309             }
8310           }
8311         }
8312         board[toY][toX] = EmptySquare;
8313       }
8314     }
8315     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8316         /* [HGM] Shogi promotions */
8317         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8318     }
8319
8320     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8321                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8322         // [HGM] superchess: take promotion piece out of holdings
8323         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8324         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8325             if(!--board[k][BOARD_WIDTH-2])
8326                 board[k][BOARD_WIDTH-1] = EmptySquare;
8327         } else {
8328             if(!--board[BOARD_HEIGHT-1-k][1])
8329                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8330         }
8331     }
8332
8333 }
8334
8335 /* Updates forwardMostMove */
8336 void
8337 MakeMove(fromX, fromY, toX, toY, promoChar)
8338      int fromX, fromY, toX, toY;
8339      int promoChar;
8340 {
8341 //    forwardMostMove++; // [HGM] bare: moved downstream
8342
8343     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8344         int timeLeft; static int lastLoadFlag=0; int king, piece;
8345         piece = boards[forwardMostMove][fromY][fromX];
8346         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8347         if(gameInfo.variant == VariantKnightmate)
8348             king += (int) WhiteUnicorn - (int) WhiteKing;
8349         if(forwardMostMove == 0) {
8350             if(blackPlaysFirst) 
8351                 fprintf(serverMoves, "%s;", second.tidy);
8352             fprintf(serverMoves, "%s;", first.tidy);
8353             if(!blackPlaysFirst) 
8354                 fprintf(serverMoves, "%s;", second.tidy);
8355         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8356         lastLoadFlag = loadFlag;
8357         // print base move
8358         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8359         // print castling suffix
8360         if( toY == fromY && piece == king ) {
8361             if(toX-fromX > 1)
8362                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8363             if(fromX-toX >1)
8364                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8365         }
8366         // e.p. suffix
8367         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8368              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8369              boards[forwardMostMove][toY][toX] == EmptySquare
8370              && fromX != toX )
8371                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8372         // promotion suffix
8373         if(promoChar != NULLCHAR)
8374                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8375         if(!loadFlag) {
8376             fprintf(serverMoves, "/%d/%d",
8377                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8378             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8379             else                      timeLeft = blackTimeRemaining/1000;
8380             fprintf(serverMoves, "/%d", timeLeft);
8381         }
8382         fflush(serverMoves);
8383     }
8384
8385     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8386       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8387                         0, 1);
8388       return;
8389     }
8390     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8391     if (commentList[forwardMostMove+1] != NULL) {
8392         free(commentList[forwardMostMove+1]);
8393         commentList[forwardMostMove+1] = NULL;
8394     }
8395     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8396     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8397     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8398     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8399     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8400     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8401     gameInfo.result = GameUnfinished;
8402     if (gameInfo.resultDetails != NULL) {
8403         free(gameInfo.resultDetails);
8404         gameInfo.resultDetails = NULL;
8405     }
8406     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8407                               moveList[forwardMostMove - 1]);
8408     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8409                              PosFlags(forwardMostMove - 1),
8410                              fromY, fromX, toY, toX, promoChar,
8411                              parseList[forwardMostMove - 1]);
8412     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8413       case MT_NONE:
8414       case MT_STALEMATE:
8415       default:
8416         break;
8417       case MT_CHECK:
8418         if(gameInfo.variant != VariantShogi)
8419             strcat(parseList[forwardMostMove - 1], "+");
8420         break;
8421       case MT_CHECKMATE:
8422       case MT_STAINMATE:
8423         strcat(parseList[forwardMostMove - 1], "#");
8424         break;
8425     }
8426     if (appData.debugMode) {
8427         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8428     }
8429
8430 }
8431
8432 /* Updates currentMove if not pausing */
8433 void
8434 ShowMove(fromX, fromY, toX, toY)
8435 {
8436     int instant = (gameMode == PlayFromGameFile) ?
8437         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8438     if(appData.noGUI) return;
8439     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8440         if (!instant) {
8441             if (forwardMostMove == currentMove + 1) {
8442                 AnimateMove(boards[forwardMostMove - 1],
8443                             fromX, fromY, toX, toY);
8444             }
8445             if (appData.highlightLastMove) {
8446                 SetHighlights(fromX, fromY, toX, toY);
8447             }
8448         }
8449         currentMove = forwardMostMove;
8450     }
8451
8452     if (instant) return;
8453
8454     DisplayMove(currentMove - 1);
8455     DrawPosition(FALSE, boards[currentMove]);
8456     DisplayBothClocks();
8457     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8458 }
8459
8460 void SendEgtPath(ChessProgramState *cps)
8461 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8462         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8463
8464         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8465
8466         while(*p) {
8467             char c, *q = name+1, *r, *s;
8468
8469             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8470             while(*p && *p != ',') *q++ = *p++;
8471             *q++ = ':'; *q = 0;
8472             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8473                 strcmp(name, ",nalimov:") == 0 ) {
8474                 // take nalimov path from the menu-changeable option first, if it is defined
8475                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8476                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8477             } else
8478             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8479                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8480                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8481                 s = r = StrStr(s, ":") + 1; // beginning of path info
8482                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8483                 c = *r; *r = 0;             // temporarily null-terminate path info
8484                     *--q = 0;               // strip of trailig ':' from name
8485                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8486                 *r = c;
8487                 SendToProgram(buf,cps);     // send egtbpath command for this format
8488             }
8489             if(*p == ',') p++; // read away comma to position for next format name
8490         }
8491 }
8492
8493 void
8494 InitChessProgram(cps, setup)
8495      ChessProgramState *cps;
8496      int setup; /* [HGM] needed to setup FRC opening position */
8497 {
8498     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8499     if (appData.noChessProgram) return;
8500     hintRequested = FALSE;
8501     bookRequested = FALSE;
8502
8503     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8504     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8505     if(cps->memSize) { /* [HGM] memory */
8506         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8507         SendToProgram(buf, cps);
8508     }
8509     SendEgtPath(cps); /* [HGM] EGT */
8510     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8511         sprintf(buf, "cores %d\n", appData.smpCores);
8512         SendToProgram(buf, cps);
8513     }
8514
8515     SendToProgram(cps->initString, cps);
8516     if (gameInfo.variant != VariantNormal &&
8517         gameInfo.variant != VariantLoadable
8518         /* [HGM] also send variant if board size non-standard */
8519         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8520                                             ) {
8521       char *v = VariantName(gameInfo.variant);
8522       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8523         /* [HGM] in protocol 1 we have to assume all variants valid */
8524         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8525         DisplayFatalError(buf, 0, 1);
8526         return;
8527       }
8528
8529       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8530       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8531       if( gameInfo.variant == VariantXiangqi )
8532            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8533       if( gameInfo.variant == VariantShogi )
8534            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8535       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8536            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8537       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8538                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8539            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8540       if( gameInfo.variant == VariantCourier )
8541            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8542       if( gameInfo.variant == VariantSuper )
8543            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8544       if( gameInfo.variant == VariantGreat )
8545            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8546
8547       if(overruled) {
8548            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8549                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8550            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8551            if(StrStr(cps->variants, b) == NULL) { 
8552                // specific sized variant not known, check if general sizing allowed
8553                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8554                    if(StrStr(cps->variants, "boardsize") == NULL) {
8555                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8556                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8557                        DisplayFatalError(buf, 0, 1);
8558                        return;
8559                    }
8560                    /* [HGM] here we really should compare with the maximum supported board size */
8561                }
8562            }
8563       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8564       sprintf(buf, "variant %s\n", b);
8565       SendToProgram(buf, cps);
8566     }
8567     currentlyInitializedVariant = gameInfo.variant;
8568
8569     /* [HGM] send opening position in FRC to first engine */
8570     if(setup) {
8571           SendToProgram("force\n", cps);
8572           SendBoard(cps, 0);
8573           /* engine is now in force mode! Set flag to wake it up after first move. */
8574           setboardSpoiledMachineBlack = 1;
8575     }
8576
8577     if (cps->sendICS) {
8578       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8579       SendToProgram(buf, cps);
8580     }
8581     cps->maybeThinking = FALSE;
8582     cps->offeredDraw = 0;
8583     if (!appData.icsActive) {
8584         SendTimeControl(cps, movesPerSession, timeControl,
8585                         timeIncrement, appData.searchDepth,
8586                         searchTime);
8587     }
8588     if (appData.showThinking 
8589         // [HGM] thinking: four options require thinking output to be sent
8590         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8591                                 ) {
8592         SendToProgram("post\n", cps);
8593     }
8594     SendToProgram("hard\n", cps);
8595     if (!appData.ponderNextMove) {
8596         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8597            it without being sure what state we are in first.  "hard"
8598            is not a toggle, so that one is OK.
8599          */
8600         SendToProgram("easy\n", cps);
8601     }
8602     if (cps->usePing) {
8603       sprintf(buf, "ping %d\n", ++cps->lastPing);
8604       SendToProgram(buf, cps);
8605     }
8606     cps->initDone = TRUE;
8607 }   
8608
8609
8610 void
8611 StartChessProgram(cps)
8612      ChessProgramState *cps;
8613 {
8614     char buf[MSG_SIZ];
8615     int err;
8616
8617     if (appData.noChessProgram) return;
8618     cps->initDone = FALSE;
8619
8620     if (strcmp(cps->host, "localhost") == 0) {
8621         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8622     } else if (*appData.remoteShell == NULLCHAR) {
8623         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8624     } else {
8625         if (*appData.remoteUser == NULLCHAR) {
8626           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8627                     cps->program);
8628         } else {
8629           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8630                     cps->host, appData.remoteUser, cps->program);
8631         }
8632         err = StartChildProcess(buf, "", &cps->pr);
8633     }
8634     
8635     if (err != 0) {
8636         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8637         DisplayFatalError(buf, err, 1);
8638         cps->pr = NoProc;
8639         cps->isr = NULL;
8640         return;
8641     }
8642     
8643     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8644     if (cps->protocolVersion > 1) {
8645       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8646       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8647       cps->comboCnt = 0;  //                and values of combo boxes
8648       SendToProgram(buf, cps);
8649     } else {
8650       SendToProgram("xboard\n", cps);
8651     }
8652 }
8653
8654
8655 void
8656 TwoMachinesEventIfReady P((void))
8657 {
8658   if (first.lastPing != first.lastPong) {
8659     DisplayMessage("", _("Waiting for first chess program"));
8660     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8661     return;
8662   }
8663   if (second.lastPing != second.lastPong) {
8664     DisplayMessage("", _("Waiting for second chess program"));
8665     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8666     return;
8667   }
8668   ThawUI();
8669   TwoMachinesEvent();
8670 }
8671
8672 void
8673 NextMatchGame P((void))
8674 {
8675     int index; /* [HGM] autoinc: step load index during match */
8676     Reset(FALSE, TRUE);
8677     if (*appData.loadGameFile != NULLCHAR) {
8678         index = appData.loadGameIndex;
8679         if(index < 0) { // [HGM] autoinc
8680             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8681             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8682         } 
8683         LoadGameFromFile(appData.loadGameFile,
8684                          index,
8685                          appData.loadGameFile, FALSE);
8686     } else if (*appData.loadPositionFile != NULLCHAR) {
8687         index = appData.loadPositionIndex;
8688         if(index < 0) { // [HGM] autoinc
8689             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8690             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8691         } 
8692         LoadPositionFromFile(appData.loadPositionFile,
8693                              index,
8694                              appData.loadPositionFile);
8695     }
8696     TwoMachinesEventIfReady();
8697 }
8698
8699 void UserAdjudicationEvent( int result )
8700 {
8701     ChessMove gameResult = GameIsDrawn;
8702
8703     if( result > 0 ) {
8704         gameResult = WhiteWins;
8705     }
8706     else if( result < 0 ) {
8707         gameResult = BlackWins;
8708     }
8709
8710     if( gameMode == TwoMachinesPlay ) {
8711         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8712     }
8713 }
8714
8715
8716 // [HGM] save: calculate checksum of game to make games easily identifiable
8717 int StringCheckSum(char *s)
8718 {
8719         int i = 0;
8720         if(s==NULL) return 0;
8721         while(*s) i = i*259 + *s++;
8722         return i;
8723 }
8724
8725 int GameCheckSum()
8726 {
8727         int i, sum=0;
8728         for(i=backwardMostMove; i<forwardMostMove; i++) {
8729                 sum += pvInfoList[i].depth;
8730                 sum += StringCheckSum(parseList[i]);
8731                 sum += StringCheckSum(commentList[i]);
8732                 sum *= 261;
8733         }
8734         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8735         return sum + StringCheckSum(commentList[i]);
8736 } // end of save patch
8737
8738 void
8739 GameEnds(result, resultDetails, whosays)
8740      ChessMove result;
8741      char *resultDetails;
8742      int whosays;
8743 {
8744     GameMode nextGameMode;
8745     int isIcsGame;
8746     char buf[MSG_SIZ];
8747
8748     if(endingGame) return; /* [HGM] crash: forbid recursion */
8749     endingGame = 1;
8750
8751     if (appData.debugMode) {
8752       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8753               result, resultDetails ? resultDetails : "(null)", whosays);
8754     }
8755
8756     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8757
8758     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8759         /* If we are playing on ICS, the server decides when the
8760            game is over, but the engine can offer to draw, claim 
8761            a draw, or resign. 
8762          */
8763 #if ZIPPY
8764         if (appData.zippyPlay && first.initDone) {
8765             if (result == GameIsDrawn) {
8766                 /* In case draw still needs to be claimed */
8767                 SendToICS(ics_prefix);
8768                 SendToICS("draw\n");
8769             } else if (StrCaseStr(resultDetails, "resign")) {
8770                 SendToICS(ics_prefix);
8771                 SendToICS("resign\n");
8772             }
8773         }
8774 #endif
8775         endingGame = 0; /* [HGM] crash */
8776         return;
8777     }
8778
8779     /* If we're loading the game from a file, stop */
8780     if (whosays == GE_FILE) {
8781       (void) StopLoadGameTimer();
8782       gameFileFP = NULL;
8783     }
8784
8785     /* Cancel draw offers */
8786     first.offeredDraw = second.offeredDraw = 0;
8787
8788     /* If this is an ICS game, only ICS can really say it's done;
8789        if not, anyone can. */
8790     isIcsGame = (gameMode == IcsPlayingWhite || 
8791                  gameMode == IcsPlayingBlack || 
8792                  gameMode == IcsObserving    || 
8793                  gameMode == IcsExamining);
8794
8795     if (!isIcsGame || whosays == GE_ICS) {
8796         /* OK -- not an ICS game, or ICS said it was done */
8797         StopClocks();
8798         if (!isIcsGame && !appData.noChessProgram) 
8799           SetUserThinkingEnables();
8800     
8801         /* [HGM] if a machine claims the game end we verify this claim */
8802         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8803             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8804                 char claimer;
8805                 ChessMove trueResult = (ChessMove) -1;
8806
8807                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8808                                             first.twoMachinesColor[0] :
8809                                             second.twoMachinesColor[0] ;
8810
8811                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8812                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8813                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8814                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8815                 } else
8816                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8817                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8818                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8819                 } else
8820                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8821                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8822                 }
8823
8824                 // now verify win claims, but not in drop games, as we don't understand those yet
8825                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8826                                                  || gameInfo.variant == VariantGreat) &&
8827                     (result == WhiteWins && claimer == 'w' ||
8828                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8829                       if (appData.debugMode) {
8830                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8831                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8832                       }
8833                       if(result != trueResult) {
8834                               sprintf(buf, "False win claim: '%s'", resultDetails);
8835                               result = claimer == 'w' ? BlackWins : WhiteWins;
8836                               resultDetails = buf;
8837                       }
8838                 } else
8839                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8840                     && (forwardMostMove <= backwardMostMove ||
8841                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8842                         (claimer=='b')==(forwardMostMove&1))
8843                                                                                   ) {
8844                       /* [HGM] verify: draws that were not flagged are false claims */
8845                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8846                       result = claimer == 'w' ? BlackWins : WhiteWins;
8847                       resultDetails = buf;
8848                 }
8849                 /* (Claiming a loss is accepted no questions asked!) */
8850             }
8851             /* [HGM] bare: don't allow bare King to win */
8852             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8853                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8854                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8855                && result != GameIsDrawn)
8856             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8857                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8858                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8859                         if(p >= 0 && p <= (int)WhiteKing) k++;
8860                 }
8861                 if (appData.debugMode) {
8862                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8863                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8864                 }
8865                 if(k <= 1) {
8866                         result = GameIsDrawn;
8867                         sprintf(buf, "%s but bare king", resultDetails);
8868                         resultDetails = buf;
8869                 }
8870             }
8871         }
8872
8873
8874         if(serverMoves != NULL && !loadFlag) { char c = '=';
8875             if(result==WhiteWins) c = '+';
8876             if(result==BlackWins) c = '-';
8877             if(resultDetails != NULL)
8878                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8879         }
8880         if (resultDetails != NULL) {
8881             gameInfo.result = result;
8882             gameInfo.resultDetails = StrSave(resultDetails);
8883
8884             /* display last move only if game was not loaded from file */
8885             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8886                 DisplayMove(currentMove - 1);
8887     
8888             if (forwardMostMove != 0) {
8889                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8890                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8891                                                                 ) {
8892                     if (*appData.saveGameFile != NULLCHAR) {
8893                         SaveGameToFile(appData.saveGameFile, TRUE);
8894                     } else if (appData.autoSaveGames) {
8895                         AutoSaveGame();
8896                     }
8897                     if (*appData.savePositionFile != NULLCHAR) {
8898                         SavePositionToFile(appData.savePositionFile);
8899                     }
8900                 }
8901             }
8902
8903             /* Tell program how game ended in case it is learning */
8904             /* [HGM] Moved this to after saving the PGN, just in case */
8905             /* engine died and we got here through time loss. In that */
8906             /* case we will get a fatal error writing the pipe, which */
8907             /* would otherwise lose us the PGN.                       */
8908             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8909             /* output during GameEnds should never be fatal anymore   */
8910             if (gameMode == MachinePlaysWhite ||
8911                 gameMode == MachinePlaysBlack ||
8912                 gameMode == TwoMachinesPlay ||
8913                 gameMode == IcsPlayingWhite ||
8914                 gameMode == IcsPlayingBlack ||
8915                 gameMode == BeginningOfGame) {
8916                 char buf[MSG_SIZ];
8917                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8918                         resultDetails);
8919                 if (first.pr != NoProc) {
8920                     SendToProgram(buf, &first);
8921                 }
8922                 if (second.pr != NoProc &&
8923                     gameMode == TwoMachinesPlay) {
8924                     SendToProgram(buf, &second);
8925                 }
8926             }
8927         }
8928
8929         if (appData.icsActive) {
8930             if (appData.quietPlay &&
8931                 (gameMode == IcsPlayingWhite ||
8932                  gameMode == IcsPlayingBlack)) {
8933                 SendToICS(ics_prefix);
8934                 SendToICS("set shout 1\n");
8935             }
8936             nextGameMode = IcsIdle;
8937             ics_user_moved = FALSE;
8938             /* clean up premove.  It's ugly when the game has ended and the
8939              * premove highlights are still on the board.
8940              */
8941             if (gotPremove) {
8942               gotPremove = FALSE;
8943               ClearPremoveHighlights();
8944               DrawPosition(FALSE, boards[currentMove]);
8945             }
8946             if (whosays == GE_ICS) {
8947                 switch (result) {
8948                 case WhiteWins:
8949                     if (gameMode == IcsPlayingWhite)
8950                         PlayIcsWinSound();
8951                     else if(gameMode == IcsPlayingBlack)
8952                         PlayIcsLossSound();
8953                     break;
8954                 case BlackWins:
8955                     if (gameMode == IcsPlayingBlack)
8956                         PlayIcsWinSound();
8957                     else if(gameMode == IcsPlayingWhite)
8958                         PlayIcsLossSound();
8959                     break;
8960                 case GameIsDrawn:
8961                     PlayIcsDrawSound();
8962                     break;
8963                 default:
8964                     PlayIcsUnfinishedSound();
8965                 }
8966             }
8967         } else if (gameMode == EditGame ||
8968                    gameMode == PlayFromGameFile || 
8969                    gameMode == AnalyzeMode || 
8970                    gameMode == AnalyzeFile) {
8971             nextGameMode = gameMode;
8972         } else {
8973             nextGameMode = EndOfGame;
8974         }
8975         pausing = FALSE;
8976         ModeHighlight();
8977     } else {
8978         nextGameMode = gameMode;
8979     }
8980
8981     if (appData.noChessProgram) {
8982         gameMode = nextGameMode;
8983         ModeHighlight();
8984         endingGame = 0; /* [HGM] crash */
8985         return;
8986     }
8987
8988     if (first.reuse) {
8989         /* Put first chess program into idle state */
8990         if (first.pr != NoProc &&
8991             (gameMode == MachinePlaysWhite ||
8992              gameMode == MachinePlaysBlack ||
8993              gameMode == TwoMachinesPlay ||
8994              gameMode == IcsPlayingWhite ||
8995              gameMode == IcsPlayingBlack ||
8996              gameMode == BeginningOfGame)) {
8997             SendToProgram("force\n", &first);
8998             if (first.usePing) {
8999               char buf[MSG_SIZ];
9000               sprintf(buf, "ping %d\n", ++first.lastPing);
9001               SendToProgram(buf, &first);
9002             }
9003         }
9004     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9005         /* Kill off first chess program */
9006         if (first.isr != NULL)
9007           RemoveInputSource(first.isr);
9008         first.isr = NULL;
9009     
9010         if (first.pr != NoProc) {
9011             ExitAnalyzeMode();
9012             DoSleep( appData.delayBeforeQuit );
9013             SendToProgram("quit\n", &first);
9014             DoSleep( appData.delayAfterQuit );
9015             DestroyChildProcess(first.pr, first.useSigterm);
9016         }
9017         first.pr = NoProc;
9018     }
9019     if (second.reuse) {
9020         /* Put second chess program into idle state */
9021         if (second.pr != NoProc &&
9022             gameMode == TwoMachinesPlay) {
9023             SendToProgram("force\n", &second);
9024             if (second.usePing) {
9025               char buf[MSG_SIZ];
9026               sprintf(buf, "ping %d\n", ++second.lastPing);
9027               SendToProgram(buf, &second);
9028             }
9029         }
9030     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9031         /* Kill off second chess program */
9032         if (second.isr != NULL)
9033           RemoveInputSource(second.isr);
9034         second.isr = NULL;
9035     
9036         if (second.pr != NoProc) {
9037             DoSleep( appData.delayBeforeQuit );
9038             SendToProgram("quit\n", &second);
9039             DoSleep( appData.delayAfterQuit );
9040             DestroyChildProcess(second.pr, second.useSigterm);
9041         }
9042         second.pr = NoProc;
9043     }
9044
9045     if (matchMode && gameMode == TwoMachinesPlay) {
9046         switch (result) {
9047         case WhiteWins:
9048           if (first.twoMachinesColor[0] == 'w') {
9049             first.matchWins++;
9050           } else {
9051             second.matchWins++;
9052           }
9053           break;
9054         case BlackWins:
9055           if (first.twoMachinesColor[0] == 'b') {
9056             first.matchWins++;
9057           } else {
9058             second.matchWins++;
9059           }
9060           break;
9061         default:
9062           break;
9063         }
9064         if (matchGame < appData.matchGames) {
9065             char *tmp;
9066             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9067                 tmp = first.twoMachinesColor;
9068                 first.twoMachinesColor = second.twoMachinesColor;
9069                 second.twoMachinesColor = tmp;
9070             }
9071             gameMode = nextGameMode;
9072             matchGame++;
9073             if(appData.matchPause>10000 || appData.matchPause<10)
9074                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9075             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9076             endingGame = 0; /* [HGM] crash */
9077             return;
9078         } else {
9079             char buf[MSG_SIZ];
9080             gameMode = nextGameMode;
9081             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9082                     first.tidy, second.tidy,
9083                     first.matchWins, second.matchWins,
9084                     appData.matchGames - (first.matchWins + second.matchWins));
9085             DisplayFatalError(buf, 0, 0);
9086         }
9087     }
9088     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9089         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9090       ExitAnalyzeMode();
9091     gameMode = nextGameMode;
9092     ModeHighlight();
9093     endingGame = 0;  /* [HGM] crash */
9094 }
9095
9096 /* Assumes program was just initialized (initString sent).
9097    Leaves program in force mode. */
9098 void
9099 FeedMovesToProgram(cps, upto) 
9100      ChessProgramState *cps;
9101      int upto;
9102 {
9103     int i;
9104     
9105     if (appData.debugMode)
9106       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9107               startedFromSetupPosition ? "position and " : "",
9108               backwardMostMove, upto, cps->which);
9109     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9110         // [HGM] variantswitch: make engine aware of new variant
9111         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9112                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9113         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9114         SendToProgram(buf, cps);
9115         currentlyInitializedVariant = gameInfo.variant;
9116     }
9117     SendToProgram("force\n", cps);
9118     if (startedFromSetupPosition) {
9119         SendBoard(cps, backwardMostMove);
9120     if (appData.debugMode) {
9121         fprintf(debugFP, "feedMoves\n");
9122     }
9123     }
9124     for (i = backwardMostMove; i < upto; i++) {
9125         SendMoveToProgram(i, cps);
9126     }
9127 }
9128
9129
9130 void
9131 ResurrectChessProgram()
9132 {
9133      /* The chess program may have exited.
9134         If so, restart it and feed it all the moves made so far. */
9135
9136     if (appData.noChessProgram || first.pr != NoProc) return;
9137     
9138     StartChessProgram(&first);
9139     InitChessProgram(&first, FALSE);
9140     FeedMovesToProgram(&first, currentMove);
9141
9142     if (!first.sendTime) {
9143         /* can't tell gnuchess what its clock should read,
9144            so we bow to its notion. */
9145         ResetClocks();
9146         timeRemaining[0][currentMove] = whiteTimeRemaining;
9147         timeRemaining[1][currentMove] = blackTimeRemaining;
9148     }
9149
9150     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9151                 appData.icsEngineAnalyze) && first.analysisSupport) {
9152       SendToProgram("analyze\n", &first);
9153       first.analyzing = TRUE;
9154     }
9155 }
9156
9157 /*
9158  * Button procedures
9159  */
9160 void
9161 Reset(redraw, init)
9162      int redraw, init;
9163 {
9164     int i;
9165
9166     if (appData.debugMode) {
9167         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9168                 redraw, init, gameMode);
9169     }
9170     CleanupTail(); // [HGM] vari: delete any stored variations
9171     pausing = pauseExamInvalid = FALSE;
9172     startedFromSetupPosition = blackPlaysFirst = FALSE;
9173     firstMove = TRUE;
9174     whiteFlag = blackFlag = FALSE;
9175     userOfferedDraw = FALSE;
9176     hintRequested = bookRequested = FALSE;
9177     first.maybeThinking = FALSE;
9178     second.maybeThinking = FALSE;
9179     first.bookSuspend = FALSE; // [HGM] book
9180     second.bookSuspend = FALSE;
9181     thinkOutput[0] = NULLCHAR;
9182     lastHint[0] = NULLCHAR;
9183     ClearGameInfo(&gameInfo);
9184     gameInfo.variant = StringToVariant(appData.variant);
9185     ics_user_moved = ics_clock_paused = FALSE;
9186     ics_getting_history = H_FALSE;
9187     ics_gamenum = -1;
9188     white_holding[0] = black_holding[0] = NULLCHAR;
9189     ClearProgramStats();
9190     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9191     
9192     ResetFrontEnd();
9193     ClearHighlights();
9194     flipView = appData.flipView;
9195     ClearPremoveHighlights();
9196     gotPremove = FALSE;
9197     alarmSounded = FALSE;
9198
9199     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9200     if(appData.serverMovesName != NULL) {
9201         /* [HGM] prepare to make moves file for broadcasting */
9202         clock_t t = clock();
9203         if(serverMoves != NULL) fclose(serverMoves);
9204         serverMoves = fopen(appData.serverMovesName, "r");
9205         if(serverMoves != NULL) {
9206             fclose(serverMoves);
9207             /* delay 15 sec before overwriting, so all clients can see end */
9208             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9209         }
9210         serverMoves = fopen(appData.serverMovesName, "w");
9211     }
9212
9213     ExitAnalyzeMode();
9214     gameMode = BeginningOfGame;
9215     ModeHighlight();
9216     if(appData.icsActive) gameInfo.variant = VariantNormal;
9217     currentMove = forwardMostMove = backwardMostMove = 0;
9218     InitPosition(redraw);
9219     for (i = 0; i < MAX_MOVES; i++) {
9220         if (commentList[i] != NULL) {
9221             free(commentList[i]);
9222             commentList[i] = NULL;
9223         }
9224     }
9225     ResetClocks();
9226     timeRemaining[0][0] = whiteTimeRemaining;
9227     timeRemaining[1][0] = blackTimeRemaining;
9228     if (first.pr == NULL) {
9229         StartChessProgram(&first);
9230     }
9231     if (init) {
9232             InitChessProgram(&first, startedFromSetupPosition);
9233     }
9234     DisplayTitle("");
9235     DisplayMessage("", "");
9236     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9237     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9238 }
9239
9240 void
9241 AutoPlayGameLoop()
9242 {
9243     for (;;) {
9244         if (!AutoPlayOneMove())
9245           return;
9246         if (matchMode || appData.timeDelay == 0)
9247           continue;
9248         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9249           return;
9250         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9251         break;
9252     }
9253 }
9254
9255
9256 int
9257 AutoPlayOneMove()
9258 {
9259     int fromX, fromY, toX, toY;
9260
9261     if (appData.debugMode) {
9262       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9263     }
9264
9265     if (gameMode != PlayFromGameFile)
9266       return FALSE;
9267
9268     if (currentMove >= forwardMostMove) {
9269       gameMode = EditGame;
9270       ModeHighlight();
9271
9272       /* [AS] Clear current move marker at the end of a game */
9273       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9274
9275       return FALSE;
9276     }
9277     
9278     toX = moveList[currentMove][2] - AAA;
9279     toY = moveList[currentMove][3] - ONE;
9280
9281     if (moveList[currentMove][1] == '@') {
9282         if (appData.highlightLastMove) {
9283             SetHighlights(-1, -1, toX, toY);
9284         }
9285     } else {
9286         fromX = moveList[currentMove][0] - AAA;
9287         fromY = moveList[currentMove][1] - ONE;
9288
9289         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9290
9291         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9292
9293         if (appData.highlightLastMove) {
9294             SetHighlights(fromX, fromY, toX, toY);
9295         }
9296     }
9297     DisplayMove(currentMove);
9298     SendMoveToProgram(currentMove++, &first);
9299     DisplayBothClocks();
9300     DrawPosition(FALSE, boards[currentMove]);
9301     // [HGM] PV info: always display, routine tests if empty
9302     DisplayComment(currentMove - 1, commentList[currentMove]);
9303     return TRUE;
9304 }
9305
9306
9307 int
9308 LoadGameOneMove(readAhead)
9309      ChessMove readAhead;
9310 {
9311     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9312     char promoChar = NULLCHAR;
9313     ChessMove moveType;
9314     char move[MSG_SIZ];
9315     char *p, *q;
9316     
9317     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9318         gameMode != AnalyzeMode && gameMode != Training) {
9319         gameFileFP = NULL;
9320         return FALSE;
9321     }
9322     
9323     yyboardindex = forwardMostMove;
9324     if (readAhead != (ChessMove)0) {
9325       moveType = readAhead;
9326     } else {
9327       if (gameFileFP == NULL)
9328           return FALSE;
9329       moveType = (ChessMove) yylex();
9330     }
9331     
9332     done = FALSE;
9333     switch (moveType) {
9334       case Comment:
9335         if (appData.debugMode) 
9336           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9337         p = yy_text;
9338
9339         /* append the comment but don't display it */
9340         AppendComment(currentMove, p, FALSE);
9341         return TRUE;
9342
9343       case WhiteCapturesEnPassant:
9344       case BlackCapturesEnPassant:
9345       case WhitePromotionChancellor:
9346       case BlackPromotionChancellor:
9347       case WhitePromotionArchbishop:
9348       case BlackPromotionArchbishop:
9349       case WhitePromotionCentaur:
9350       case BlackPromotionCentaur:
9351       case WhitePromotionQueen:
9352       case BlackPromotionQueen:
9353       case WhitePromotionRook:
9354       case BlackPromotionRook:
9355       case WhitePromotionBishop:
9356       case BlackPromotionBishop:
9357       case WhitePromotionKnight:
9358       case BlackPromotionKnight:
9359       case WhitePromotionKing:
9360       case BlackPromotionKing:
9361       case NormalMove:
9362       case WhiteKingSideCastle:
9363       case WhiteQueenSideCastle:
9364       case BlackKingSideCastle:
9365       case BlackQueenSideCastle:
9366       case WhiteKingSideCastleWild:
9367       case WhiteQueenSideCastleWild:
9368       case BlackKingSideCastleWild:
9369       case BlackQueenSideCastleWild:
9370       /* PUSH Fabien */
9371       case WhiteHSideCastleFR:
9372       case WhiteASideCastleFR:
9373       case BlackHSideCastleFR:
9374       case BlackASideCastleFR:
9375       /* POP Fabien */
9376         if (appData.debugMode)
9377           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9378         fromX = currentMoveString[0] - AAA;
9379         fromY = currentMoveString[1] - ONE;
9380         toX = currentMoveString[2] - AAA;
9381         toY = currentMoveString[3] - ONE;
9382         promoChar = currentMoveString[4];
9383         break;
9384
9385       case WhiteDrop:
9386       case BlackDrop:
9387         if (appData.debugMode)
9388           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9389         fromX = moveType == WhiteDrop ?
9390           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9391         (int) CharToPiece(ToLower(currentMoveString[0]));
9392         fromY = DROP_RANK;
9393         toX = currentMoveString[2] - AAA;
9394         toY = currentMoveString[3] - ONE;
9395         break;
9396
9397       case WhiteWins:
9398       case BlackWins:
9399       case GameIsDrawn:
9400       case GameUnfinished:
9401         if (appData.debugMode)
9402           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9403         p = strchr(yy_text, '{');
9404         if (p == NULL) p = strchr(yy_text, '(');
9405         if (p == NULL) {
9406             p = yy_text;
9407             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9408         } else {
9409             q = strchr(p, *p == '{' ? '}' : ')');
9410             if (q != NULL) *q = NULLCHAR;
9411             p++;
9412         }
9413         GameEnds(moveType, p, GE_FILE);
9414         done = TRUE;
9415         if (cmailMsgLoaded) {
9416             ClearHighlights();
9417             flipView = WhiteOnMove(currentMove);
9418             if (moveType == GameUnfinished) flipView = !flipView;
9419             if (appData.debugMode)
9420               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9421         }
9422         break;
9423
9424       case (ChessMove) 0:       /* end of file */
9425         if (appData.debugMode)
9426           fprintf(debugFP, "Parser hit end of file\n");
9427         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9428           case MT_NONE:
9429           case MT_CHECK:
9430             break;
9431           case MT_CHECKMATE:
9432           case MT_STAINMATE:
9433             if (WhiteOnMove(currentMove)) {
9434                 GameEnds(BlackWins, "Black mates", GE_FILE);
9435             } else {
9436                 GameEnds(WhiteWins, "White mates", GE_FILE);
9437             }
9438             break;
9439           case MT_STALEMATE:
9440             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9441             break;
9442         }
9443         done = TRUE;
9444         break;
9445
9446       case MoveNumberOne:
9447         if (lastLoadGameStart == GNUChessGame) {
9448             /* GNUChessGames have numbers, but they aren't move numbers */
9449             if (appData.debugMode)
9450               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9451                       yy_text, (int) moveType);
9452             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9453         }
9454         /* else fall thru */
9455
9456       case XBoardGame:
9457       case GNUChessGame:
9458       case PGNTag:
9459         /* Reached start of next game in file */
9460         if (appData.debugMode)
9461           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9462         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9463           case MT_NONE:
9464           case MT_CHECK:
9465             break;
9466           case MT_CHECKMATE:
9467           case MT_STAINMATE:
9468             if (WhiteOnMove(currentMove)) {
9469                 GameEnds(BlackWins, "Black mates", GE_FILE);
9470             } else {
9471                 GameEnds(WhiteWins, "White mates", GE_FILE);
9472             }
9473             break;
9474           case MT_STALEMATE:
9475             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9476             break;
9477         }
9478         done = TRUE;
9479         break;
9480
9481       case PositionDiagram:     /* should not happen; ignore */
9482       case ElapsedTime:         /* ignore */
9483       case NAG:                 /* ignore */
9484         if (appData.debugMode)
9485           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9486                   yy_text, (int) moveType);
9487         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9488
9489       case IllegalMove:
9490         if (appData.testLegality) {
9491             if (appData.debugMode)
9492               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9493             sprintf(move, _("Illegal move: %d.%s%s"),
9494                     (forwardMostMove / 2) + 1,
9495                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9496             DisplayError(move, 0);
9497             done = TRUE;
9498         } else {
9499             if (appData.debugMode)
9500               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9501                       yy_text, currentMoveString);
9502             fromX = currentMoveString[0] - AAA;
9503             fromY = currentMoveString[1] - ONE;
9504             toX = currentMoveString[2] - AAA;
9505             toY = currentMoveString[3] - ONE;
9506             promoChar = currentMoveString[4];
9507         }
9508         break;
9509
9510       case AmbiguousMove:
9511         if (appData.debugMode)
9512           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9513         sprintf(move, _("Ambiguous move: %d.%s%s"),
9514                 (forwardMostMove / 2) + 1,
9515                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9516         DisplayError(move, 0);
9517         done = TRUE;
9518         break;
9519
9520       default:
9521       case ImpossibleMove:
9522         if (appData.debugMode)
9523           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9524         sprintf(move, _("Illegal move: %d.%s%s"),
9525                 (forwardMostMove / 2) + 1,
9526                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9527         DisplayError(move, 0);
9528         done = TRUE;
9529         break;
9530     }
9531
9532     if (done) {
9533         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9534             DrawPosition(FALSE, boards[currentMove]);
9535             DisplayBothClocks();
9536             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9537               DisplayComment(currentMove - 1, commentList[currentMove]);
9538         }
9539         (void) StopLoadGameTimer();
9540         gameFileFP = NULL;
9541         cmailOldMove = forwardMostMove;
9542         return FALSE;
9543     } else {
9544         /* currentMoveString is set as a side-effect of yylex */
9545         strcat(currentMoveString, "\n");
9546         strcpy(moveList[forwardMostMove], currentMoveString);
9547         
9548         thinkOutput[0] = NULLCHAR;
9549         MakeMove(fromX, fromY, toX, toY, promoChar);
9550         currentMove = forwardMostMove;
9551         return TRUE;
9552     }
9553 }
9554
9555 /* Load the nth game from the given file */
9556 int
9557 LoadGameFromFile(filename, n, title, useList)
9558      char *filename;
9559      int n;
9560      char *title;
9561      /*Boolean*/ int useList;
9562 {
9563     FILE *f;
9564     char buf[MSG_SIZ];
9565
9566     if (strcmp(filename, "-") == 0) {
9567         f = stdin;
9568         title = "stdin";
9569     } else {
9570         f = fopen(filename, "rb");
9571         if (f == NULL) {
9572           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9573             DisplayError(buf, errno);
9574             return FALSE;
9575         }
9576     }
9577     if (fseek(f, 0, 0) == -1) {
9578         /* f is not seekable; probably a pipe */
9579         useList = FALSE;
9580     }
9581     if (useList && n == 0) {
9582         int error = GameListBuild(f);
9583         if (error) {
9584             DisplayError(_("Cannot build game list"), error);
9585         } else if (!ListEmpty(&gameList) &&
9586                    ((ListGame *) gameList.tailPred)->number > 1) {
9587             GameListPopUp(f, title);
9588             return TRUE;
9589         }
9590         GameListDestroy();
9591         n = 1;
9592     }
9593     if (n == 0) n = 1;
9594     return LoadGame(f, n, title, FALSE);
9595 }
9596
9597
9598 void
9599 MakeRegisteredMove()
9600 {
9601     int fromX, fromY, toX, toY;
9602     char promoChar;
9603     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9604         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9605           case CMAIL_MOVE:
9606           case CMAIL_DRAW:
9607             if (appData.debugMode)
9608               fprintf(debugFP, "Restoring %s for game %d\n",
9609                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9610     
9611             thinkOutput[0] = NULLCHAR;
9612             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9613             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9614             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9615             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9616             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9617             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9618             MakeMove(fromX, fromY, toX, toY, promoChar);
9619             ShowMove(fromX, fromY, toX, toY);
9620               
9621             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9622               case MT_NONE:
9623               case MT_CHECK:
9624                 break;
9625                 
9626               case MT_CHECKMATE:
9627               case MT_STAINMATE:
9628                 if (WhiteOnMove(currentMove)) {
9629                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9630                 } else {
9631                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9632                 }
9633                 break;
9634                 
9635               case MT_STALEMATE:
9636                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9637                 break;
9638             }
9639
9640             break;
9641             
9642           case CMAIL_RESIGN:
9643             if (WhiteOnMove(currentMove)) {
9644                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9645             } else {
9646                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9647             }
9648             break;
9649             
9650           case CMAIL_ACCEPT:
9651             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9652             break;
9653               
9654           default:
9655             break;
9656         }
9657     }
9658
9659     return;
9660 }
9661
9662 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9663 int
9664 CmailLoadGame(f, gameNumber, title, useList)
9665      FILE *f;
9666      int gameNumber;
9667      char *title;
9668      int useList;
9669 {
9670     int retVal;
9671
9672     if (gameNumber > nCmailGames) {
9673         DisplayError(_("No more games in this message"), 0);
9674         return FALSE;
9675     }
9676     if (f == lastLoadGameFP) {
9677         int offset = gameNumber - lastLoadGameNumber;
9678         if (offset == 0) {
9679             cmailMsg[0] = NULLCHAR;
9680             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9681                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9682                 nCmailMovesRegistered--;
9683             }
9684             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9685             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9686                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9687             }
9688         } else {
9689             if (! RegisterMove()) return FALSE;
9690         }
9691     }
9692
9693     retVal = LoadGame(f, gameNumber, title, useList);
9694
9695     /* Make move registered during previous look at this game, if any */
9696     MakeRegisteredMove();
9697
9698     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9699         commentList[currentMove]
9700           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9701         DisplayComment(currentMove - 1, commentList[currentMove]);
9702     }
9703
9704     return retVal;
9705 }
9706
9707 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9708 int
9709 ReloadGame(offset)
9710      int offset;
9711 {
9712     int gameNumber = lastLoadGameNumber + offset;
9713     if (lastLoadGameFP == NULL) {
9714         DisplayError(_("No game has been loaded yet"), 0);
9715         return FALSE;
9716     }
9717     if (gameNumber <= 0) {
9718         DisplayError(_("Can't back up any further"), 0);
9719         return FALSE;
9720     }
9721     if (cmailMsgLoaded) {
9722         return CmailLoadGame(lastLoadGameFP, gameNumber,
9723                              lastLoadGameTitle, lastLoadGameUseList);
9724     } else {
9725         return LoadGame(lastLoadGameFP, gameNumber,
9726                         lastLoadGameTitle, lastLoadGameUseList);
9727     }
9728 }
9729
9730
9731
9732 /* Load the nth game from open file f */
9733 int
9734 LoadGame(f, gameNumber, title, useList)
9735      FILE *f;
9736      int gameNumber;
9737      char *title;
9738      int useList;
9739 {
9740     ChessMove cm;
9741     char buf[MSG_SIZ];
9742     int gn = gameNumber;
9743     ListGame *lg = NULL;
9744     int numPGNTags = 0;
9745     int err;
9746     GameMode oldGameMode;
9747     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9748
9749     if (appData.debugMode) 
9750         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9751
9752     if (gameMode == Training )
9753         SetTrainingModeOff();
9754
9755     oldGameMode = gameMode;
9756     if (gameMode != BeginningOfGame) {
9757       Reset(FALSE, TRUE);
9758     }
9759
9760     gameFileFP = f;
9761     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9762         fclose(lastLoadGameFP);
9763     }
9764
9765     if (useList) {
9766         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9767         
9768         if (lg) {
9769             fseek(f, lg->offset, 0);
9770             GameListHighlight(gameNumber);
9771             gn = 1;
9772         }
9773         else {
9774             DisplayError(_("Game number out of range"), 0);
9775             return FALSE;
9776         }
9777     } else {
9778         GameListDestroy();
9779         if (fseek(f, 0, 0) == -1) {
9780             if (f == lastLoadGameFP ?
9781                 gameNumber == lastLoadGameNumber + 1 :
9782                 gameNumber == 1) {
9783                 gn = 1;
9784             } else {
9785                 DisplayError(_("Can't seek on game file"), 0);
9786                 return FALSE;
9787             }
9788         }
9789     }
9790     lastLoadGameFP = f;
9791     lastLoadGameNumber = gameNumber;
9792     strcpy(lastLoadGameTitle, title);
9793     lastLoadGameUseList = useList;
9794
9795     yynewfile(f);
9796
9797     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9798       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9799                 lg->gameInfo.black);
9800             DisplayTitle(buf);
9801     } else if (*title != NULLCHAR) {
9802         if (gameNumber > 1) {
9803             sprintf(buf, "%s %d", title, gameNumber);
9804             DisplayTitle(buf);
9805         } else {
9806             DisplayTitle(title);
9807         }
9808     }
9809
9810     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9811         gameMode = PlayFromGameFile;
9812         ModeHighlight();
9813     }
9814
9815     currentMove = forwardMostMove = backwardMostMove = 0;
9816     CopyBoard(boards[0], initialPosition);
9817     StopClocks();
9818
9819     /*
9820      * Skip the first gn-1 games in the file.
9821      * Also skip over anything that precedes an identifiable 
9822      * start of game marker, to avoid being confused by 
9823      * garbage at the start of the file.  Currently 
9824      * recognized start of game markers are the move number "1",
9825      * the pattern "gnuchess .* game", the pattern
9826      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9827      * A game that starts with one of the latter two patterns
9828      * will also have a move number 1, possibly
9829      * following a position diagram.
9830      * 5-4-02: Let's try being more lenient and allowing a game to
9831      * start with an unnumbered move.  Does that break anything?
9832      */
9833     cm = lastLoadGameStart = (ChessMove) 0;
9834     while (gn > 0) {
9835         yyboardindex = forwardMostMove;
9836         cm = (ChessMove) yylex();
9837         switch (cm) {
9838           case (ChessMove) 0:
9839             if (cmailMsgLoaded) {
9840                 nCmailGames = CMAIL_MAX_GAMES - gn;
9841             } else {
9842                 Reset(TRUE, TRUE);
9843                 DisplayError(_("Game not found in file"), 0);
9844             }
9845             return FALSE;
9846
9847           case GNUChessGame:
9848           case XBoardGame:
9849             gn--;
9850             lastLoadGameStart = cm;
9851             break;
9852             
9853           case MoveNumberOne:
9854             switch (lastLoadGameStart) {
9855               case GNUChessGame:
9856               case XBoardGame:
9857               case PGNTag:
9858                 break;
9859               case MoveNumberOne:
9860               case (ChessMove) 0:
9861                 gn--;           /* count this game */
9862                 lastLoadGameStart = cm;
9863                 break;
9864               default:
9865                 /* impossible */
9866                 break;
9867             }
9868             break;
9869
9870           case PGNTag:
9871             switch (lastLoadGameStart) {
9872               case GNUChessGame:
9873               case PGNTag:
9874               case MoveNumberOne:
9875               case (ChessMove) 0:
9876                 gn--;           /* count this game */
9877                 lastLoadGameStart = cm;
9878                 break;
9879               case XBoardGame:
9880                 lastLoadGameStart = cm; /* game counted already */
9881                 break;
9882               default:
9883                 /* impossible */
9884                 break;
9885             }
9886             if (gn > 0) {
9887                 do {
9888                     yyboardindex = forwardMostMove;
9889                     cm = (ChessMove) yylex();
9890                 } while (cm == PGNTag || cm == Comment);
9891             }
9892             break;
9893
9894           case WhiteWins:
9895           case BlackWins:
9896           case GameIsDrawn:
9897             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9898                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9899                     != CMAIL_OLD_RESULT) {
9900                     nCmailResults ++ ;
9901                     cmailResult[  CMAIL_MAX_GAMES
9902                                 - gn - 1] = CMAIL_OLD_RESULT;
9903                 }
9904             }
9905             break;
9906
9907           case NormalMove:
9908             /* Only a NormalMove can be at the start of a game
9909              * without a position diagram. */
9910             if (lastLoadGameStart == (ChessMove) 0) {
9911               gn--;
9912               lastLoadGameStart = MoveNumberOne;
9913             }
9914             break;
9915
9916           default:
9917             break;
9918         }
9919     }
9920     
9921     if (appData.debugMode)
9922       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9923
9924     if (cm == XBoardGame) {
9925         /* Skip any header junk before position diagram and/or move 1 */
9926         for (;;) {
9927             yyboardindex = forwardMostMove;
9928             cm = (ChessMove) yylex();
9929
9930             if (cm == (ChessMove) 0 ||
9931                 cm == GNUChessGame || cm == XBoardGame) {
9932                 /* Empty game; pretend end-of-file and handle later */
9933                 cm = (ChessMove) 0;
9934                 break;
9935             }
9936
9937             if (cm == MoveNumberOne || cm == PositionDiagram ||
9938                 cm == PGNTag || cm == Comment)
9939               break;
9940         }
9941     } else if (cm == GNUChessGame) {
9942         if (gameInfo.event != NULL) {
9943             free(gameInfo.event);
9944         }
9945         gameInfo.event = StrSave(yy_text);
9946     }   
9947
9948     startedFromSetupPosition = FALSE;
9949     while (cm == PGNTag) {
9950         if (appData.debugMode) 
9951           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9952         err = ParsePGNTag(yy_text, &gameInfo);
9953         if (!err) numPGNTags++;
9954
9955         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9956         if(gameInfo.variant != oldVariant) {
9957             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9958             InitPosition(TRUE);
9959             oldVariant = gameInfo.variant;
9960             if (appData.debugMode) 
9961               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9962         }
9963
9964
9965         if (gameInfo.fen != NULL) {
9966           Board initial_position;
9967           startedFromSetupPosition = TRUE;
9968           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9969             Reset(TRUE, TRUE);
9970             DisplayError(_("Bad FEN position in file"), 0);
9971             return FALSE;
9972           }
9973           CopyBoard(boards[0], initial_position);
9974           if (blackPlaysFirst) {
9975             currentMove = forwardMostMove = backwardMostMove = 1;
9976             CopyBoard(boards[1], initial_position);
9977             strcpy(moveList[0], "");
9978             strcpy(parseList[0], "");
9979             timeRemaining[0][1] = whiteTimeRemaining;
9980             timeRemaining[1][1] = blackTimeRemaining;
9981             if (commentList[0] != NULL) {
9982               commentList[1] = commentList[0];
9983               commentList[0] = NULL;
9984             }
9985           } else {
9986             currentMove = forwardMostMove = backwardMostMove = 0;
9987           }
9988           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9989           {   int i;
9990               initialRulePlies = FENrulePlies;
9991               for( i=0; i< nrCastlingRights; i++ )
9992                   initialRights[i] = initial_position[CASTLING][i];
9993           }
9994           yyboardindex = forwardMostMove;
9995           free(gameInfo.fen);
9996           gameInfo.fen = NULL;
9997         }
9998
9999         yyboardindex = forwardMostMove;
10000         cm = (ChessMove) yylex();
10001
10002         /* Handle comments interspersed among the tags */
10003         while (cm == Comment) {
10004             char *p;
10005             if (appData.debugMode) 
10006               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10007             p = yy_text;
10008             AppendComment(currentMove, p, FALSE);
10009             yyboardindex = forwardMostMove;
10010             cm = (ChessMove) yylex();
10011         }
10012     }
10013
10014     /* don't rely on existence of Event tag since if game was
10015      * pasted from clipboard the Event tag may not exist
10016      */
10017     if (numPGNTags > 0){
10018         char *tags;
10019         if (gameInfo.variant == VariantNormal) {
10020           gameInfo.variant = StringToVariant(gameInfo.event);
10021         }
10022         if (!matchMode) {
10023           if( appData.autoDisplayTags ) {
10024             tags = PGNTags(&gameInfo);
10025             TagsPopUp(tags, CmailMsg());
10026             free(tags);
10027           }
10028         }
10029     } else {
10030         /* Make something up, but don't display it now */
10031         SetGameInfo();
10032         TagsPopDown();
10033     }
10034
10035     if (cm == PositionDiagram) {
10036         int i, j;
10037         char *p;
10038         Board initial_position;
10039
10040         if (appData.debugMode)
10041           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10042
10043         if (!startedFromSetupPosition) {
10044             p = yy_text;
10045             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10046               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10047                 switch (*p) {
10048                   case '[':
10049                   case '-':
10050                   case ' ':
10051                   case '\t':
10052                   case '\n':
10053                   case '\r':
10054                     break;
10055                   default:
10056                     initial_position[i][j++] = CharToPiece(*p);
10057                     break;
10058                 }
10059             while (*p == ' ' || *p == '\t' ||
10060                    *p == '\n' || *p == '\r') p++;
10061         
10062             if (strncmp(p, "black", strlen("black"))==0)
10063               blackPlaysFirst = TRUE;
10064             else
10065               blackPlaysFirst = FALSE;
10066             startedFromSetupPosition = TRUE;
10067         
10068             CopyBoard(boards[0], initial_position);
10069             if (blackPlaysFirst) {
10070                 currentMove = forwardMostMove = backwardMostMove = 1;
10071                 CopyBoard(boards[1], initial_position);
10072                 strcpy(moveList[0], "");
10073                 strcpy(parseList[0], "");
10074                 timeRemaining[0][1] = whiteTimeRemaining;
10075                 timeRemaining[1][1] = blackTimeRemaining;
10076                 if (commentList[0] != NULL) {
10077                     commentList[1] = commentList[0];
10078                     commentList[0] = NULL;
10079                 }
10080             } else {
10081                 currentMove = forwardMostMove = backwardMostMove = 0;
10082             }
10083         }
10084         yyboardindex = forwardMostMove;
10085         cm = (ChessMove) yylex();
10086     }
10087
10088     if (first.pr == NoProc) {
10089         StartChessProgram(&first);
10090     }
10091     InitChessProgram(&first, FALSE);
10092     SendToProgram("force\n", &first);
10093     if (startedFromSetupPosition) {
10094         SendBoard(&first, forwardMostMove);
10095     if (appData.debugMode) {
10096         fprintf(debugFP, "Load Game\n");
10097     }
10098         DisplayBothClocks();
10099     }      
10100
10101     /* [HGM] server: flag to write setup moves in broadcast file as one */
10102     loadFlag = appData.suppressLoadMoves;
10103
10104     while (cm == Comment) {
10105         char *p;
10106         if (appData.debugMode) 
10107           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10108         p = yy_text;
10109         AppendComment(currentMove, p, FALSE);
10110         yyboardindex = forwardMostMove;
10111         cm = (ChessMove) yylex();
10112     }
10113
10114     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10115         cm == WhiteWins || cm == BlackWins ||
10116         cm == GameIsDrawn || cm == GameUnfinished) {
10117         DisplayMessage("", _("No moves in game"));
10118         if (cmailMsgLoaded) {
10119             if (appData.debugMode)
10120               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10121             ClearHighlights();
10122             flipView = FALSE;
10123         }
10124         DrawPosition(FALSE, boards[currentMove]);
10125         DisplayBothClocks();
10126         gameMode = EditGame;
10127         ModeHighlight();
10128         gameFileFP = NULL;
10129         cmailOldMove = 0;
10130         return TRUE;
10131     }
10132
10133     // [HGM] PV info: routine tests if comment empty
10134     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10135         DisplayComment(currentMove - 1, commentList[currentMove]);
10136     }
10137     if (!matchMode && appData.timeDelay != 0) 
10138       DrawPosition(FALSE, boards[currentMove]);
10139
10140     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10141       programStats.ok_to_send = 1;
10142     }
10143
10144     /* if the first token after the PGN tags is a move
10145      * and not move number 1, retrieve it from the parser 
10146      */
10147     if (cm != MoveNumberOne)
10148         LoadGameOneMove(cm);
10149
10150     /* load the remaining moves from the file */
10151     while (LoadGameOneMove((ChessMove)0)) {
10152       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10153       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10154     }
10155
10156     /* rewind to the start of the game */
10157     currentMove = backwardMostMove;
10158
10159     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10160
10161     if (oldGameMode == AnalyzeFile ||
10162         oldGameMode == AnalyzeMode) {
10163       AnalyzeFileEvent();
10164     }
10165
10166     if (matchMode || appData.timeDelay == 0) {
10167       ToEndEvent();
10168       gameMode = EditGame;
10169       ModeHighlight();
10170     } else if (appData.timeDelay > 0) {
10171       AutoPlayGameLoop();
10172     }
10173
10174     if (appData.debugMode) 
10175         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10176
10177     loadFlag = 0; /* [HGM] true game starts */
10178     return TRUE;
10179 }
10180
10181 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10182 int
10183 ReloadPosition(offset)
10184      int offset;
10185 {
10186     int positionNumber = lastLoadPositionNumber + offset;
10187     if (lastLoadPositionFP == NULL) {
10188         DisplayError(_("No position has been loaded yet"), 0);
10189         return FALSE;
10190     }
10191     if (positionNumber <= 0) {
10192         DisplayError(_("Can't back up any further"), 0);
10193         return FALSE;
10194     }
10195     return LoadPosition(lastLoadPositionFP, positionNumber,
10196                         lastLoadPositionTitle);
10197 }
10198
10199 /* Load the nth position from the given file */
10200 int
10201 LoadPositionFromFile(filename, n, title)
10202      char *filename;
10203      int n;
10204      char *title;
10205 {
10206     FILE *f;
10207     char buf[MSG_SIZ];
10208
10209     if (strcmp(filename, "-") == 0) {
10210         return LoadPosition(stdin, n, "stdin");
10211     } else {
10212         f = fopen(filename, "rb");
10213         if (f == NULL) {
10214             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10215             DisplayError(buf, errno);
10216             return FALSE;
10217         } else {
10218             return LoadPosition(f, n, title);
10219         }
10220     }
10221 }
10222
10223 /* Load the nth position from the given open file, and close it */
10224 int
10225 LoadPosition(f, positionNumber, title)
10226      FILE *f;
10227      int positionNumber;
10228      char *title;
10229 {
10230     char *p, line[MSG_SIZ];
10231     Board initial_position;
10232     int i, j, fenMode, pn;
10233     
10234     if (gameMode == Training )
10235         SetTrainingModeOff();
10236
10237     if (gameMode != BeginningOfGame) {
10238         Reset(FALSE, TRUE);
10239     }
10240     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10241         fclose(lastLoadPositionFP);
10242     }
10243     if (positionNumber == 0) positionNumber = 1;
10244     lastLoadPositionFP = f;
10245     lastLoadPositionNumber = positionNumber;
10246     strcpy(lastLoadPositionTitle, title);
10247     if (first.pr == NoProc) {
10248       StartChessProgram(&first);
10249       InitChessProgram(&first, FALSE);
10250     }    
10251     pn = positionNumber;
10252     if (positionNumber < 0) {
10253         /* Negative position number means to seek to that byte offset */
10254         if (fseek(f, -positionNumber, 0) == -1) {
10255             DisplayError(_("Can't seek on position file"), 0);
10256             return FALSE;
10257         };
10258         pn = 1;
10259     } else {
10260         if (fseek(f, 0, 0) == -1) {
10261             if (f == lastLoadPositionFP ?
10262                 positionNumber == lastLoadPositionNumber + 1 :
10263                 positionNumber == 1) {
10264                 pn = 1;
10265             } else {
10266                 DisplayError(_("Can't seek on position file"), 0);
10267                 return FALSE;
10268             }
10269         }
10270     }
10271     /* See if this file is FEN or old-style xboard */
10272     if (fgets(line, MSG_SIZ, f) == NULL) {
10273         DisplayError(_("Position not found in file"), 0);
10274         return FALSE;
10275     }
10276     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10277     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10278
10279     if (pn >= 2) {
10280         if (fenMode || line[0] == '#') pn--;
10281         while (pn > 0) {
10282             /* skip positions before number pn */
10283             if (fgets(line, MSG_SIZ, f) == NULL) {
10284                 Reset(TRUE, TRUE);
10285                 DisplayError(_("Position not found in file"), 0);
10286                 return FALSE;
10287             }
10288             if (fenMode || line[0] == '#') pn--;
10289         }
10290     }
10291
10292     if (fenMode) {
10293         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10294             DisplayError(_("Bad FEN position in file"), 0);
10295             return FALSE;
10296         }
10297     } else {
10298         (void) fgets(line, MSG_SIZ, f);
10299         (void) fgets(line, MSG_SIZ, f);
10300     
10301         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10302             (void) fgets(line, MSG_SIZ, f);
10303             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10304                 if (*p == ' ')
10305                   continue;
10306                 initial_position[i][j++] = CharToPiece(*p);
10307             }
10308         }
10309     
10310         blackPlaysFirst = FALSE;
10311         if (!feof(f)) {
10312             (void) fgets(line, MSG_SIZ, f);
10313             if (strncmp(line, "black", strlen("black"))==0)
10314               blackPlaysFirst = TRUE;
10315         }
10316     }
10317     startedFromSetupPosition = TRUE;
10318     
10319     SendToProgram("force\n", &first);
10320     CopyBoard(boards[0], initial_position);
10321     if (blackPlaysFirst) {
10322         currentMove = forwardMostMove = backwardMostMove = 1;
10323         strcpy(moveList[0], "");
10324         strcpy(parseList[0], "");
10325         CopyBoard(boards[1], initial_position);
10326         DisplayMessage("", _("Black to play"));
10327     } else {
10328         currentMove = forwardMostMove = backwardMostMove = 0;
10329         DisplayMessage("", _("White to play"));
10330     }
10331     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10332     SendBoard(&first, forwardMostMove);
10333     if (appData.debugMode) {
10334 int i, j;
10335   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10336   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10337         fprintf(debugFP, "Load Position\n");
10338     }
10339
10340     if (positionNumber > 1) {
10341         sprintf(line, "%s %d", title, positionNumber);
10342         DisplayTitle(line);
10343     } else {
10344         DisplayTitle(title);
10345     }
10346     gameMode = EditGame;
10347     ModeHighlight();
10348     ResetClocks();
10349     timeRemaining[0][1] = whiteTimeRemaining;
10350     timeRemaining[1][1] = blackTimeRemaining;
10351     DrawPosition(FALSE, boards[currentMove]);
10352    
10353     return TRUE;
10354 }
10355
10356
10357 void
10358 CopyPlayerNameIntoFileName(dest, src)
10359      char **dest, *src;
10360 {
10361     while (*src != NULLCHAR && *src != ',') {
10362         if (*src == ' ') {
10363             *(*dest)++ = '_';
10364             src++;
10365         } else {
10366             *(*dest)++ = *src++;
10367         }
10368     }
10369 }
10370
10371 char *DefaultFileName(ext)
10372      char *ext;
10373 {
10374     static char def[MSG_SIZ];
10375     char *p;
10376
10377     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10378         p = def;
10379         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10380         *p++ = '-';
10381         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10382         *p++ = '.';
10383         strcpy(p, ext);
10384     } else {
10385         def[0] = NULLCHAR;
10386     }
10387     return def;
10388 }
10389
10390 /* Save the current game to the given file */
10391 int
10392 SaveGameToFile(filename, append)
10393      char *filename;
10394      int append;
10395 {
10396     FILE *f;
10397     char buf[MSG_SIZ];
10398
10399     if (strcmp(filename, "-") == 0) {
10400         return SaveGame(stdout, 0, NULL);
10401     } else {
10402         f = fopen(filename, append ? "a" : "w");
10403         if (f == NULL) {
10404             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10405             DisplayError(buf, errno);
10406             return FALSE;
10407         } else {
10408             return SaveGame(f, 0, NULL);
10409         }
10410     }
10411 }
10412
10413 char *
10414 SavePart(str)
10415      char *str;
10416 {
10417     static char buf[MSG_SIZ];
10418     char *p;
10419     
10420     p = strchr(str, ' ');
10421     if (p == NULL) return str;
10422     strncpy(buf, str, p - str);
10423     buf[p - str] = NULLCHAR;
10424     return buf;
10425 }
10426
10427 #define PGN_MAX_LINE 75
10428
10429 #define PGN_SIDE_WHITE  0
10430 #define PGN_SIDE_BLACK  1
10431
10432 /* [AS] */
10433 static int FindFirstMoveOutOfBook( int side )
10434 {
10435     int result = -1;
10436
10437     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10438         int index = backwardMostMove;
10439         int has_book_hit = 0;
10440
10441         if( (index % 2) != side ) {
10442             index++;
10443         }
10444
10445         while( index < forwardMostMove ) {
10446             /* Check to see if engine is in book */
10447             int depth = pvInfoList[index].depth;
10448             int score = pvInfoList[index].score;
10449             int in_book = 0;
10450
10451             if( depth <= 2 ) {
10452                 in_book = 1;
10453             }
10454             else if( score == 0 && depth == 63 ) {
10455                 in_book = 1; /* Zappa */
10456             }
10457             else if( score == 2 && depth == 99 ) {
10458                 in_book = 1; /* Abrok */
10459             }
10460
10461             has_book_hit += in_book;
10462
10463             if( ! in_book ) {
10464                 result = index;
10465
10466                 break;
10467             }
10468
10469             index += 2;
10470         }
10471     }
10472
10473     return result;
10474 }
10475
10476 /* [AS] */
10477 void GetOutOfBookInfo( char * buf )
10478 {
10479     int oob[2];
10480     int i;
10481     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10482
10483     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10484     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10485
10486     *buf = '\0';
10487
10488     if( oob[0] >= 0 || oob[1] >= 0 ) {
10489         for( i=0; i<2; i++ ) {
10490             int idx = oob[i];
10491
10492             if( idx >= 0 ) {
10493                 if( i > 0 && oob[0] >= 0 ) {
10494                     strcat( buf, "   " );
10495                 }
10496
10497                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10498                 sprintf( buf+strlen(buf), "%s%.2f", 
10499                     pvInfoList[idx].score >= 0 ? "+" : "",
10500                     pvInfoList[idx].score / 100.0 );
10501             }
10502         }
10503     }
10504 }
10505
10506 /* Save game in PGN style and close the file */
10507 int
10508 SaveGamePGN(f)
10509      FILE *f;
10510 {
10511     int i, offset, linelen, newblock;
10512     time_t tm;
10513 //    char *movetext;
10514     char numtext[32];
10515     int movelen, numlen, blank;
10516     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10517
10518     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10519     
10520     tm = time((time_t *) NULL);
10521     
10522     PrintPGNTags(f, &gameInfo);
10523     
10524     if (backwardMostMove > 0 || startedFromSetupPosition) {
10525         char *fen = PositionToFEN(backwardMostMove, NULL);
10526         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10527         fprintf(f, "\n{--------------\n");
10528         PrintPosition(f, backwardMostMove);
10529         fprintf(f, "--------------}\n");
10530         free(fen);
10531     }
10532     else {
10533         /* [AS] Out of book annotation */
10534         if( appData.saveOutOfBookInfo ) {
10535             char buf[64];
10536
10537             GetOutOfBookInfo( buf );
10538
10539             if( buf[0] != '\0' ) {
10540                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10541             }
10542         }
10543
10544         fprintf(f, "\n");
10545     }
10546
10547     i = backwardMostMove;
10548     linelen = 0;
10549     newblock = TRUE;
10550
10551     while (i < forwardMostMove) {
10552         /* Print comments preceding this move */
10553         if (commentList[i] != NULL) {
10554             if (linelen > 0) fprintf(f, "\n");
10555             fprintf(f, "%s", commentList[i]);
10556             linelen = 0;
10557             newblock = TRUE;
10558         }
10559
10560         /* Format move number */
10561         if ((i % 2) == 0) {
10562             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10563         } else {
10564             if (newblock) {
10565                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10566             } else {
10567                 numtext[0] = NULLCHAR;
10568             }
10569         }
10570         numlen = strlen(numtext);
10571         newblock = FALSE;
10572
10573         /* Print move number */
10574         blank = linelen > 0 && numlen > 0;
10575         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10576             fprintf(f, "\n");
10577             linelen = 0;
10578             blank = 0;
10579         }
10580         if (blank) {
10581             fprintf(f, " ");
10582             linelen++;
10583         }
10584         fprintf(f, "%s", numtext);
10585         linelen += numlen;
10586
10587         /* Get move */
10588         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10589         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10590
10591         /* Print move */
10592         blank = linelen > 0 && movelen > 0;
10593         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10594             fprintf(f, "\n");
10595             linelen = 0;
10596             blank = 0;
10597         }
10598         if (blank) {
10599             fprintf(f, " ");
10600             linelen++;
10601         }
10602         fprintf(f, "%s", move_buffer);
10603         linelen += movelen;
10604
10605         /* [AS] Add PV info if present */
10606         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10607             /* [HGM] add time */
10608             char buf[MSG_SIZ]; int seconds;
10609
10610             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10611
10612             if( seconds <= 0) buf[0] = 0; else
10613             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10614                 seconds = (seconds + 4)/10; // round to full seconds
10615                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10616                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10617             }
10618
10619             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10620                 pvInfoList[i].score >= 0 ? "+" : "",
10621                 pvInfoList[i].score / 100.0,
10622                 pvInfoList[i].depth,
10623                 buf );
10624
10625             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10626
10627             /* Print score/depth */
10628             blank = linelen > 0 && movelen > 0;
10629             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10630                 fprintf(f, "\n");
10631                 linelen = 0;
10632                 blank = 0;
10633             }
10634             if (blank) {
10635                 fprintf(f, " ");
10636                 linelen++;
10637             }
10638             fprintf(f, "%s", move_buffer);
10639             linelen += movelen;
10640         }
10641
10642         i++;
10643     }
10644     
10645     /* Start a new line */
10646     if (linelen > 0) fprintf(f, "\n");
10647
10648     /* Print comments after last move */
10649     if (commentList[i] != NULL) {
10650         fprintf(f, "%s\n", commentList[i]);
10651     }
10652
10653     /* Print result */
10654     if (gameInfo.resultDetails != NULL &&
10655         gameInfo.resultDetails[0] != NULLCHAR) {
10656         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10657                 PGNResult(gameInfo.result));
10658     } else {
10659         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10660     }
10661
10662     fclose(f);
10663     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10664     return TRUE;
10665 }
10666
10667 /* Save game in old style and close the file */
10668 int
10669 SaveGameOldStyle(f)
10670      FILE *f;
10671 {
10672     int i, offset;
10673     time_t tm;
10674     
10675     tm = time((time_t *) NULL);
10676     
10677     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10678     PrintOpponents(f);
10679     
10680     if (backwardMostMove > 0 || startedFromSetupPosition) {
10681         fprintf(f, "\n[--------------\n");
10682         PrintPosition(f, backwardMostMove);
10683         fprintf(f, "--------------]\n");
10684     } else {
10685         fprintf(f, "\n");
10686     }
10687
10688     i = backwardMostMove;
10689     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10690
10691     while (i < forwardMostMove) {
10692         if (commentList[i] != NULL) {
10693             fprintf(f, "[%s]\n", commentList[i]);
10694         }
10695
10696         if ((i % 2) == 1) {
10697             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10698             i++;
10699         } else {
10700             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10701             i++;
10702             if (commentList[i] != NULL) {
10703                 fprintf(f, "\n");
10704                 continue;
10705             }
10706             if (i >= forwardMostMove) {
10707                 fprintf(f, "\n");
10708                 break;
10709             }
10710             fprintf(f, "%s\n", parseList[i]);
10711             i++;
10712         }
10713     }
10714     
10715     if (commentList[i] != NULL) {
10716         fprintf(f, "[%s]\n", commentList[i]);
10717     }
10718
10719     /* This isn't really the old style, but it's close enough */
10720     if (gameInfo.resultDetails != NULL &&
10721         gameInfo.resultDetails[0] != NULLCHAR) {
10722         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10723                 gameInfo.resultDetails);
10724     } else {
10725         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10726     }
10727
10728     fclose(f);
10729     return TRUE;
10730 }
10731
10732 /* Save the current game to open file f and close the file */
10733 int
10734 SaveGame(f, dummy, dummy2)
10735      FILE *f;
10736      int dummy;
10737      char *dummy2;
10738 {
10739     if (gameMode == EditPosition) EditPositionDone(TRUE);
10740     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10741     if (appData.oldSaveStyle)
10742       return SaveGameOldStyle(f);
10743     else
10744       return SaveGamePGN(f);
10745 }
10746
10747 /* Save the current position to the given file */
10748 int
10749 SavePositionToFile(filename)
10750      char *filename;
10751 {
10752     FILE *f;
10753     char buf[MSG_SIZ];
10754
10755     if (strcmp(filename, "-") == 0) {
10756         return SavePosition(stdout, 0, NULL);
10757     } else {
10758         f = fopen(filename, "a");
10759         if (f == NULL) {
10760             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10761             DisplayError(buf, errno);
10762             return FALSE;
10763         } else {
10764             SavePosition(f, 0, NULL);
10765             return TRUE;
10766         }
10767     }
10768 }
10769
10770 /* Save the current position to the given open file and close the file */
10771 int
10772 SavePosition(f, dummy, dummy2)
10773      FILE *f;
10774      int dummy;
10775      char *dummy2;
10776 {
10777     time_t tm;
10778     char *fen;
10779     
10780     if (gameMode == EditPosition) EditPositionDone(TRUE);
10781     if (appData.oldSaveStyle) {
10782         tm = time((time_t *) NULL);
10783     
10784         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10785         PrintOpponents(f);
10786         fprintf(f, "[--------------\n");
10787         PrintPosition(f, currentMove);
10788         fprintf(f, "--------------]\n");
10789     } else {
10790         fen = PositionToFEN(currentMove, NULL);
10791         fprintf(f, "%s\n", fen);
10792         free(fen);
10793     }
10794     fclose(f);
10795     return TRUE;
10796 }
10797
10798 void
10799 ReloadCmailMsgEvent(unregister)
10800      int unregister;
10801 {
10802 #if !WIN32
10803     static char *inFilename = NULL;
10804     static char *outFilename;
10805     int i;
10806     struct stat inbuf, outbuf;
10807     int status;
10808     
10809     /* Any registered moves are unregistered if unregister is set, */
10810     /* i.e. invoked by the signal handler */
10811     if (unregister) {
10812         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10813             cmailMoveRegistered[i] = FALSE;
10814             if (cmailCommentList[i] != NULL) {
10815                 free(cmailCommentList[i]);
10816                 cmailCommentList[i] = NULL;
10817             }
10818         }
10819         nCmailMovesRegistered = 0;
10820     }
10821
10822     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10823         cmailResult[i] = CMAIL_NOT_RESULT;
10824     }
10825     nCmailResults = 0;
10826
10827     if (inFilename == NULL) {
10828         /* Because the filenames are static they only get malloced once  */
10829         /* and they never get freed                                      */
10830         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10831         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10832
10833         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10834         sprintf(outFilename, "%s.out", appData.cmailGameName);
10835     }
10836     
10837     status = stat(outFilename, &outbuf);
10838     if (status < 0) {
10839         cmailMailedMove = FALSE;
10840     } else {
10841         status = stat(inFilename, &inbuf);
10842         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10843     }
10844     
10845     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10846        counts the games, notes how each one terminated, etc.
10847        
10848        It would be nice to remove this kludge and instead gather all
10849        the information while building the game list.  (And to keep it
10850        in the game list nodes instead of having a bunch of fixed-size
10851        parallel arrays.)  Note this will require getting each game's
10852        termination from the PGN tags, as the game list builder does
10853        not process the game moves.  --mann
10854        */
10855     cmailMsgLoaded = TRUE;
10856     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10857     
10858     /* Load first game in the file or popup game menu */
10859     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10860
10861 #endif /* !WIN32 */
10862     return;
10863 }
10864
10865 int
10866 RegisterMove()
10867 {
10868     FILE *f;
10869     char string[MSG_SIZ];
10870
10871     if (   cmailMailedMove
10872         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10873         return TRUE;            /* Allow free viewing  */
10874     }
10875
10876     /* Unregister move to ensure that we don't leave RegisterMove        */
10877     /* with the move registered when the conditions for registering no   */
10878     /* longer hold                                                       */
10879     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10880         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10881         nCmailMovesRegistered --;
10882
10883         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10884           {
10885               free(cmailCommentList[lastLoadGameNumber - 1]);
10886               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10887           }
10888     }
10889
10890     if (cmailOldMove == -1) {
10891         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10892         return FALSE;
10893     }
10894
10895     if (currentMove > cmailOldMove + 1) {
10896         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10897         return FALSE;
10898     }
10899
10900     if (currentMove < cmailOldMove) {
10901         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10902         return FALSE;
10903     }
10904
10905     if (forwardMostMove > currentMove) {
10906         /* Silently truncate extra moves */
10907         TruncateGame();
10908     }
10909
10910     if (   (currentMove == cmailOldMove + 1)
10911         || (   (currentMove == cmailOldMove)
10912             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10913                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10914         if (gameInfo.result != GameUnfinished) {
10915             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10916         }
10917
10918         if (commentList[currentMove] != NULL) {
10919             cmailCommentList[lastLoadGameNumber - 1]
10920               = StrSave(commentList[currentMove]);
10921         }
10922         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10923
10924         if (appData.debugMode)
10925           fprintf(debugFP, "Saving %s for game %d\n",
10926                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10927
10928         sprintf(string,
10929                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10930         
10931         f = fopen(string, "w");
10932         if (appData.oldSaveStyle) {
10933             SaveGameOldStyle(f); /* also closes the file */
10934             
10935             sprintf(string, "%s.pos.out", appData.cmailGameName);
10936             f = fopen(string, "w");
10937             SavePosition(f, 0, NULL); /* also closes the file */
10938         } else {
10939             fprintf(f, "{--------------\n");
10940             PrintPosition(f, currentMove);
10941             fprintf(f, "--------------}\n\n");
10942             
10943             SaveGame(f, 0, NULL); /* also closes the file*/
10944         }
10945         
10946         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10947         nCmailMovesRegistered ++;
10948     } else if (nCmailGames == 1) {
10949         DisplayError(_("You have not made a move yet"), 0);
10950         return FALSE;
10951     }
10952
10953     return TRUE;
10954 }
10955
10956 void
10957 MailMoveEvent()
10958 {
10959 #if !WIN32
10960     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10961     FILE *commandOutput;
10962     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10963     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10964     int nBuffers;
10965     int i;
10966     int archived;
10967     char *arcDir;
10968
10969     if (! cmailMsgLoaded) {
10970         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10971         return;
10972     }
10973
10974     if (nCmailGames == nCmailResults) {
10975         DisplayError(_("No unfinished games"), 0);
10976         return;
10977     }
10978
10979 #if CMAIL_PROHIBIT_REMAIL
10980     if (cmailMailedMove) {
10981         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);
10982         DisplayError(msg, 0);
10983         return;
10984     }
10985 #endif
10986
10987     if (! (cmailMailedMove || RegisterMove())) return;
10988     
10989     if (   cmailMailedMove
10990         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10991         sprintf(string, partCommandString,
10992                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10993         commandOutput = popen(string, "r");
10994
10995         if (commandOutput == NULL) {
10996             DisplayError(_("Failed to invoke cmail"), 0);
10997         } else {
10998             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10999                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11000             }
11001             if (nBuffers > 1) {
11002                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11003                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11004                 nBytes = MSG_SIZ - 1;
11005             } else {
11006                 (void) memcpy(msg, buffer, nBytes);
11007             }
11008             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11009
11010             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11011                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11012
11013                 archived = TRUE;
11014                 for (i = 0; i < nCmailGames; i ++) {
11015                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11016                         archived = FALSE;
11017                     }
11018                 }
11019                 if (   archived
11020                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11021                         != NULL)) {
11022                     sprintf(buffer, "%s/%s.%s.archive",
11023                             arcDir,
11024                             appData.cmailGameName,
11025                             gameInfo.date);
11026                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11027                     cmailMsgLoaded = FALSE;
11028                 }
11029             }
11030
11031             DisplayInformation(msg);
11032             pclose(commandOutput);
11033         }
11034     } else {
11035         if ((*cmailMsg) != '\0') {
11036             DisplayInformation(cmailMsg);
11037         }
11038     }
11039
11040     return;
11041 #endif /* !WIN32 */
11042 }
11043
11044 char *
11045 CmailMsg()
11046 {
11047 #if WIN32
11048     return NULL;
11049 #else
11050     int  prependComma = 0;
11051     char number[5];
11052     char string[MSG_SIZ];       /* Space for game-list */
11053     int  i;
11054     
11055     if (!cmailMsgLoaded) return "";
11056
11057     if (cmailMailedMove) {
11058         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11059     } else {
11060         /* Create a list of games left */
11061         sprintf(string, "[");
11062         for (i = 0; i < nCmailGames; i ++) {
11063             if (! (   cmailMoveRegistered[i]
11064                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11065                 if (prependComma) {
11066                     sprintf(number, ",%d", i + 1);
11067                 } else {
11068                     sprintf(number, "%d", i + 1);
11069                     prependComma = 1;
11070                 }
11071                 
11072                 strcat(string, number);
11073             }
11074         }
11075         strcat(string, "]");
11076
11077         if (nCmailMovesRegistered + nCmailResults == 0) {
11078             switch (nCmailGames) {
11079               case 1:
11080                 sprintf(cmailMsg,
11081                         _("Still need to make move for game\n"));
11082                 break;
11083                 
11084               case 2:
11085                 sprintf(cmailMsg,
11086                         _("Still need to make moves for both games\n"));
11087                 break;
11088                 
11089               default:
11090                 sprintf(cmailMsg,
11091                         _("Still need to make moves for all %d games\n"),
11092                         nCmailGames);
11093                 break;
11094             }
11095         } else {
11096             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11097               case 1:
11098                 sprintf(cmailMsg,
11099                         _("Still need to make a move for game %s\n"),
11100                         string);
11101                 break;
11102                 
11103               case 0:
11104                 if (nCmailResults == nCmailGames) {
11105                     sprintf(cmailMsg, _("No unfinished games\n"));
11106                 } else {
11107                     sprintf(cmailMsg, _("Ready to send mail\n"));
11108                 }
11109                 break;
11110                 
11111               default:
11112                 sprintf(cmailMsg,
11113                         _("Still need to make moves for games %s\n"),
11114                         string);
11115             }
11116         }
11117     }
11118     return cmailMsg;
11119 #endif /* WIN32 */
11120 }
11121
11122 void
11123 ResetGameEvent()
11124 {
11125     if (gameMode == Training)
11126       SetTrainingModeOff();
11127
11128     Reset(TRUE, TRUE);
11129     cmailMsgLoaded = FALSE;
11130     if (appData.icsActive) {
11131       SendToICS(ics_prefix);
11132       SendToICS("refresh\n");
11133     }
11134 }
11135
11136 void
11137 ExitEvent(status)
11138      int status;
11139 {
11140     exiting++;
11141     if (exiting > 2) {
11142       /* Give up on clean exit */
11143       exit(status);
11144     }
11145     if (exiting > 1) {
11146       /* Keep trying for clean exit */
11147       return;
11148     }
11149
11150     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11151
11152     if (telnetISR != NULL) {
11153       RemoveInputSource(telnetISR);
11154     }
11155     if (icsPR != NoProc) {
11156       DestroyChildProcess(icsPR, TRUE);
11157     }
11158
11159     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11160     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11161
11162     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11163     /* make sure this other one finishes before killing it!                  */
11164     if(endingGame) { int count = 0;
11165         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11166         while(endingGame && count++ < 10) DoSleep(1);
11167         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11168     }
11169
11170     /* Kill off chess programs */
11171     if (first.pr != NoProc) {
11172         ExitAnalyzeMode();
11173         
11174         DoSleep( appData.delayBeforeQuit );
11175         SendToProgram("quit\n", &first);
11176         DoSleep( appData.delayAfterQuit );
11177         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11178     }
11179     if (second.pr != NoProc) {
11180         DoSleep( appData.delayBeforeQuit );
11181         SendToProgram("quit\n", &second);
11182         DoSleep( appData.delayAfterQuit );
11183         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11184     }
11185     if (first.isr != NULL) {
11186         RemoveInputSource(first.isr);
11187     }
11188     if (second.isr != NULL) {
11189         RemoveInputSource(second.isr);
11190     }
11191
11192     ShutDownFrontEnd();
11193     exit(status);
11194 }
11195
11196 void
11197 PauseEvent()
11198 {
11199     if (appData.debugMode)
11200         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11201     if (pausing) {
11202         pausing = FALSE;
11203         ModeHighlight();
11204         if (gameMode == MachinePlaysWhite ||
11205             gameMode == MachinePlaysBlack) {
11206             StartClocks();
11207         } else {
11208             DisplayBothClocks();
11209         }
11210         if (gameMode == PlayFromGameFile) {
11211             if (appData.timeDelay >= 0) 
11212                 AutoPlayGameLoop();
11213         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11214             Reset(FALSE, TRUE);
11215             SendToICS(ics_prefix);
11216             SendToICS("refresh\n");
11217         } else if (currentMove < forwardMostMove) {
11218             ForwardInner(forwardMostMove);
11219         }
11220         pauseExamInvalid = FALSE;
11221     } else {
11222         switch (gameMode) {
11223           default:
11224             return;
11225           case IcsExamining:
11226             pauseExamForwardMostMove = forwardMostMove;
11227             pauseExamInvalid = FALSE;
11228             /* fall through */
11229           case IcsObserving:
11230           case IcsPlayingWhite:
11231           case IcsPlayingBlack:
11232             pausing = TRUE;
11233             ModeHighlight();
11234             return;
11235           case PlayFromGameFile:
11236             (void) StopLoadGameTimer();
11237             pausing = TRUE;
11238             ModeHighlight();
11239             break;
11240           case BeginningOfGame:
11241             if (appData.icsActive) return;
11242             /* else fall through */
11243           case MachinePlaysWhite:
11244           case MachinePlaysBlack:
11245           case TwoMachinesPlay:
11246             if (forwardMostMove == 0)
11247               return;           /* don't pause if no one has moved */
11248             if ((gameMode == MachinePlaysWhite &&
11249                  !WhiteOnMove(forwardMostMove)) ||
11250                 (gameMode == MachinePlaysBlack &&
11251                  WhiteOnMove(forwardMostMove))) {
11252                 StopClocks();
11253             }
11254             pausing = TRUE;
11255             ModeHighlight();
11256             break;
11257         }
11258     }
11259 }
11260
11261 void
11262 EditCommentEvent()
11263 {
11264     char title[MSG_SIZ];
11265
11266     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11267         strcpy(title, _("Edit comment"));
11268     } else {
11269         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11270                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11271                 parseList[currentMove - 1]);
11272     }
11273
11274     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11275 }
11276
11277
11278 void
11279 EditTagsEvent()
11280 {
11281     char *tags = PGNTags(&gameInfo);
11282     EditTagsPopUp(tags);
11283     free(tags);
11284 }
11285
11286 void
11287 AnalyzeModeEvent()
11288 {
11289     if (appData.noChessProgram || gameMode == AnalyzeMode)
11290       return;
11291
11292     if (gameMode != AnalyzeFile) {
11293         if (!appData.icsEngineAnalyze) {
11294                EditGameEvent();
11295                if (gameMode != EditGame) return;
11296         }
11297         ResurrectChessProgram();
11298         SendToProgram("analyze\n", &first);
11299         first.analyzing = TRUE;
11300         /*first.maybeThinking = TRUE;*/
11301         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11302         EngineOutputPopUp();
11303     }
11304     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11305     pausing = FALSE;
11306     ModeHighlight();
11307     SetGameInfo();
11308
11309     StartAnalysisClock();
11310     GetTimeMark(&lastNodeCountTime);
11311     lastNodeCount = 0;
11312 }
11313
11314 void
11315 AnalyzeFileEvent()
11316 {
11317     if (appData.noChessProgram || gameMode == AnalyzeFile)
11318       return;
11319
11320     if (gameMode != AnalyzeMode) {
11321         EditGameEvent();
11322         if (gameMode != EditGame) return;
11323         ResurrectChessProgram();
11324         SendToProgram("analyze\n", &first);
11325         first.analyzing = TRUE;
11326         /*first.maybeThinking = TRUE;*/
11327         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11328         EngineOutputPopUp();
11329     }
11330     gameMode = AnalyzeFile;
11331     pausing = FALSE;
11332     ModeHighlight();
11333     SetGameInfo();
11334
11335     StartAnalysisClock();
11336     GetTimeMark(&lastNodeCountTime);
11337     lastNodeCount = 0;
11338 }
11339
11340 void
11341 MachineWhiteEvent()
11342 {
11343     char buf[MSG_SIZ];
11344     char *bookHit = NULL;
11345
11346     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11347       return;
11348
11349
11350     if (gameMode == PlayFromGameFile || 
11351         gameMode == TwoMachinesPlay  || 
11352         gameMode == Training         || 
11353         gameMode == AnalyzeMode      || 
11354         gameMode == EndOfGame)
11355         EditGameEvent();
11356
11357     if (gameMode == EditPosition) 
11358         EditPositionDone(TRUE);
11359
11360     if (!WhiteOnMove(currentMove)) {
11361         DisplayError(_("It is not White's turn"), 0);
11362         return;
11363     }
11364   
11365     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11366       ExitAnalyzeMode();
11367
11368     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11369         gameMode == AnalyzeFile)
11370         TruncateGame();
11371
11372     ResurrectChessProgram();    /* in case it isn't running */
11373     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11374         gameMode = MachinePlaysWhite;
11375         ResetClocks();
11376     } else
11377     gameMode = MachinePlaysWhite;
11378     pausing = FALSE;
11379     ModeHighlight();
11380     SetGameInfo();
11381     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11382     DisplayTitle(buf);
11383     if (first.sendName) {
11384       sprintf(buf, "name %s\n", gameInfo.black);
11385       SendToProgram(buf, &first);
11386     }
11387     if (first.sendTime) {
11388       if (first.useColors) {
11389         SendToProgram("black\n", &first); /*gnu kludge*/
11390       }
11391       SendTimeRemaining(&first, TRUE);
11392     }
11393     if (first.useColors) {
11394       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11395     }
11396     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11397     SetMachineThinkingEnables();
11398     first.maybeThinking = TRUE;
11399     StartClocks();
11400     firstMove = FALSE;
11401
11402     if (appData.autoFlipView && !flipView) {
11403       flipView = !flipView;
11404       DrawPosition(FALSE, NULL);
11405       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11406     }
11407
11408     if(bookHit) { // [HGM] book: simulate book reply
11409         static char bookMove[MSG_SIZ]; // a bit generous?
11410
11411         programStats.nodes = programStats.depth = programStats.time = 
11412         programStats.score = programStats.got_only_move = 0;
11413         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11414
11415         strcpy(bookMove, "move ");
11416         strcat(bookMove, bookHit);
11417         HandleMachineMove(bookMove, &first);
11418     }
11419 }
11420
11421 void
11422 MachineBlackEvent()
11423 {
11424     char buf[MSG_SIZ];
11425    char *bookHit = NULL;
11426
11427     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11428         return;
11429
11430
11431     if (gameMode == PlayFromGameFile || 
11432         gameMode == TwoMachinesPlay  || 
11433         gameMode == Training         || 
11434         gameMode == AnalyzeMode      || 
11435         gameMode == EndOfGame)
11436         EditGameEvent();
11437
11438     if (gameMode == EditPosition) 
11439         EditPositionDone(TRUE);
11440
11441     if (WhiteOnMove(currentMove)) {
11442         DisplayError(_("It is not Black's turn"), 0);
11443         return;
11444     }
11445     
11446     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11447       ExitAnalyzeMode();
11448
11449     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11450         gameMode == AnalyzeFile)
11451         TruncateGame();
11452
11453     ResurrectChessProgram();    /* in case it isn't running */
11454     gameMode = MachinePlaysBlack;
11455     pausing = FALSE;
11456     ModeHighlight();
11457     SetGameInfo();
11458     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11459     DisplayTitle(buf);
11460     if (first.sendName) {
11461       sprintf(buf, "name %s\n", gameInfo.white);
11462       SendToProgram(buf, &first);
11463     }
11464     if (first.sendTime) {
11465       if (first.useColors) {
11466         SendToProgram("white\n", &first); /*gnu kludge*/
11467       }
11468       SendTimeRemaining(&first, FALSE);
11469     }
11470     if (first.useColors) {
11471       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11472     }
11473     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11474     SetMachineThinkingEnables();
11475     first.maybeThinking = TRUE;
11476     StartClocks();
11477
11478     if (appData.autoFlipView && flipView) {
11479       flipView = !flipView;
11480       DrawPosition(FALSE, NULL);
11481       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11482     }
11483     if(bookHit) { // [HGM] book: simulate book reply
11484         static char bookMove[MSG_SIZ]; // a bit generous?
11485
11486         programStats.nodes = programStats.depth = programStats.time = 
11487         programStats.score = programStats.got_only_move = 0;
11488         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11489
11490         strcpy(bookMove, "move ");
11491         strcat(bookMove, bookHit);
11492         HandleMachineMove(bookMove, &first);
11493     }
11494 }
11495
11496
11497 void
11498 DisplayTwoMachinesTitle()
11499 {
11500     char buf[MSG_SIZ];
11501     if (appData.matchGames > 0) {
11502         if (first.twoMachinesColor[0] == 'w') {
11503             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11504                     gameInfo.white, gameInfo.black,
11505                     first.matchWins, second.matchWins,
11506                     matchGame - 1 - (first.matchWins + second.matchWins));
11507         } else {
11508             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11509                     gameInfo.white, gameInfo.black,
11510                     second.matchWins, first.matchWins,
11511                     matchGame - 1 - (first.matchWins + second.matchWins));
11512         }
11513     } else {
11514         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11515     }
11516     DisplayTitle(buf);
11517 }
11518
11519 void
11520 TwoMachinesEvent P((void))
11521 {
11522     int i;
11523     char buf[MSG_SIZ];
11524     ChessProgramState *onmove;
11525     char *bookHit = NULL;
11526     
11527     if (appData.noChessProgram) return;
11528
11529     switch (gameMode) {
11530       case TwoMachinesPlay:
11531         return;
11532       case MachinePlaysWhite:
11533       case MachinePlaysBlack:
11534         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11535             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11536             return;
11537         }
11538         /* fall through */
11539       case BeginningOfGame:
11540       case PlayFromGameFile:
11541       case EndOfGame:
11542         EditGameEvent();
11543         if (gameMode != EditGame) return;
11544         break;
11545       case EditPosition:
11546         EditPositionDone(TRUE);
11547         break;
11548       case AnalyzeMode:
11549       case AnalyzeFile:
11550         ExitAnalyzeMode();
11551         break;
11552       case EditGame:
11553       default:
11554         break;
11555     }
11556
11557 //    forwardMostMove = currentMove;
11558     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11559     ResurrectChessProgram();    /* in case first program isn't running */
11560
11561     if (second.pr == NULL) {
11562         StartChessProgram(&second);
11563         if (second.protocolVersion == 1) {
11564           TwoMachinesEventIfReady();
11565         } else {
11566           /* kludge: allow timeout for initial "feature" command */
11567           FreezeUI();
11568           DisplayMessage("", _("Starting second chess program"));
11569           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11570         }
11571         return;
11572     }
11573     DisplayMessage("", "");
11574     InitChessProgram(&second, FALSE);
11575     SendToProgram("force\n", &second);
11576     if (startedFromSetupPosition) {
11577         SendBoard(&second, backwardMostMove);
11578     if (appData.debugMode) {
11579         fprintf(debugFP, "Two Machines\n");
11580     }
11581     }
11582     for (i = backwardMostMove; i < forwardMostMove; i++) {
11583         SendMoveToProgram(i, &second);
11584     }
11585
11586     gameMode = TwoMachinesPlay;
11587     pausing = FALSE;
11588     ModeHighlight();
11589     SetGameInfo();
11590     DisplayTwoMachinesTitle();
11591     firstMove = TRUE;
11592     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11593         onmove = &first;
11594     } else {
11595         onmove = &second;
11596     }
11597
11598     SendToProgram(first.computerString, &first);
11599     if (first.sendName) {
11600       sprintf(buf, "name %s\n", second.tidy);
11601       SendToProgram(buf, &first);
11602     }
11603     SendToProgram(second.computerString, &second);
11604     if (second.sendName) {
11605       sprintf(buf, "name %s\n", first.tidy);
11606       SendToProgram(buf, &second);
11607     }
11608
11609     ResetClocks();
11610     if (!first.sendTime || !second.sendTime) {
11611         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11612         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11613     }
11614     if (onmove->sendTime) {
11615       if (onmove->useColors) {
11616         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11617       }
11618       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11619     }
11620     if (onmove->useColors) {
11621       SendToProgram(onmove->twoMachinesColor, onmove);
11622     }
11623     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11624 //    SendToProgram("go\n", onmove);
11625     onmove->maybeThinking = TRUE;
11626     SetMachineThinkingEnables();
11627
11628     StartClocks();
11629
11630     if(bookHit) { // [HGM] book: simulate book reply
11631         static char bookMove[MSG_SIZ]; // a bit generous?
11632
11633         programStats.nodes = programStats.depth = programStats.time = 
11634         programStats.score = programStats.got_only_move = 0;
11635         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11636
11637         strcpy(bookMove, "move ");
11638         strcat(bookMove, bookHit);
11639         savedMessage = bookMove; // args for deferred call
11640         savedState = onmove;
11641         ScheduleDelayedEvent(DeferredBookMove, 1);
11642     }
11643 }
11644
11645 void
11646 TrainingEvent()
11647 {
11648     if (gameMode == Training) {
11649       SetTrainingModeOff();
11650       gameMode = PlayFromGameFile;
11651       DisplayMessage("", _("Training mode off"));
11652     } else {
11653       gameMode = Training;
11654       animateTraining = appData.animate;
11655
11656       /* make sure we are not already at the end of the game */
11657       if (currentMove < forwardMostMove) {
11658         SetTrainingModeOn();
11659         DisplayMessage("", _("Training mode on"));
11660       } else {
11661         gameMode = PlayFromGameFile;
11662         DisplayError(_("Already at end of game"), 0);
11663       }
11664     }
11665     ModeHighlight();
11666 }
11667
11668 void
11669 IcsClientEvent()
11670 {
11671     if (!appData.icsActive) return;
11672     switch (gameMode) {
11673       case IcsPlayingWhite:
11674       case IcsPlayingBlack:
11675       case IcsObserving:
11676       case IcsIdle:
11677       case BeginningOfGame:
11678       case IcsExamining:
11679         return;
11680
11681       case EditGame:
11682         break;
11683
11684       case EditPosition:
11685         EditPositionDone(TRUE);
11686         break;
11687
11688       case AnalyzeMode:
11689       case AnalyzeFile:
11690         ExitAnalyzeMode();
11691         break;
11692         
11693       default:
11694         EditGameEvent();
11695         break;
11696     }
11697
11698     gameMode = IcsIdle;
11699     ModeHighlight();
11700     return;
11701 }
11702
11703
11704 void
11705 EditGameEvent()
11706 {
11707     int i;
11708
11709     switch (gameMode) {
11710       case Training:
11711         SetTrainingModeOff();
11712         break;
11713       case MachinePlaysWhite:
11714       case MachinePlaysBlack:
11715       case BeginningOfGame:
11716         SendToProgram("force\n", &first);
11717         SetUserThinkingEnables();
11718         break;
11719       case PlayFromGameFile:
11720         (void) StopLoadGameTimer();
11721         if (gameFileFP != NULL) {
11722             gameFileFP = NULL;
11723         }
11724         break;
11725       case EditPosition:
11726         EditPositionDone(TRUE);
11727         break;
11728       case AnalyzeMode:
11729       case AnalyzeFile:
11730         ExitAnalyzeMode();
11731         SendToProgram("force\n", &first);
11732         break;
11733       case TwoMachinesPlay:
11734         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11735         ResurrectChessProgram();
11736         SetUserThinkingEnables();
11737         break;
11738       case EndOfGame:
11739         ResurrectChessProgram();
11740         break;
11741       case IcsPlayingBlack:
11742       case IcsPlayingWhite:
11743         DisplayError(_("Warning: You are still playing a game"), 0);
11744         break;
11745       case IcsObserving:
11746         DisplayError(_("Warning: You are still observing a game"), 0);
11747         break;
11748       case IcsExamining:
11749         DisplayError(_("Warning: You are still examining a game"), 0);
11750         break;
11751       case IcsIdle:
11752         break;
11753       case EditGame:
11754       default:
11755         return;
11756     }
11757     
11758     pausing = FALSE;
11759     StopClocks();
11760     first.offeredDraw = second.offeredDraw = 0;
11761
11762     if (gameMode == PlayFromGameFile) {
11763         whiteTimeRemaining = timeRemaining[0][currentMove];
11764         blackTimeRemaining = timeRemaining[1][currentMove];
11765         DisplayTitle("");
11766     }
11767
11768     if (gameMode == MachinePlaysWhite ||
11769         gameMode == MachinePlaysBlack ||
11770         gameMode == TwoMachinesPlay ||
11771         gameMode == EndOfGame) {
11772         i = forwardMostMove;
11773         while (i > currentMove) {
11774             SendToProgram("undo\n", &first);
11775             i--;
11776         }
11777         whiteTimeRemaining = timeRemaining[0][currentMove];
11778         blackTimeRemaining = timeRemaining[1][currentMove];
11779         DisplayBothClocks();
11780         if (whiteFlag || blackFlag) {
11781             whiteFlag = blackFlag = 0;
11782         }
11783         DisplayTitle("");
11784     }           
11785     
11786     gameMode = EditGame;
11787     ModeHighlight();
11788     SetGameInfo();
11789 }
11790
11791
11792 void
11793 EditPositionEvent()
11794 {
11795     if (gameMode == EditPosition) {
11796         EditGameEvent();
11797         return;
11798     }
11799     
11800     EditGameEvent();
11801     if (gameMode != EditGame) return;
11802     
11803     gameMode = EditPosition;
11804     ModeHighlight();
11805     SetGameInfo();
11806     if (currentMove > 0)
11807       CopyBoard(boards[0], boards[currentMove]);
11808     
11809     blackPlaysFirst = !WhiteOnMove(currentMove);
11810     ResetClocks();
11811     currentMove = forwardMostMove = backwardMostMove = 0;
11812     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11813     DisplayMove(-1);
11814 }
11815
11816 void
11817 ExitAnalyzeMode()
11818 {
11819     /* [DM] icsEngineAnalyze - possible call from other functions */
11820     if (appData.icsEngineAnalyze) {
11821         appData.icsEngineAnalyze = FALSE;
11822
11823         DisplayMessage("",_("Close ICS engine analyze..."));
11824     }
11825     if (first.analysisSupport && first.analyzing) {
11826       SendToProgram("exit\n", &first);
11827       first.analyzing = FALSE;
11828     }
11829     thinkOutput[0] = NULLCHAR;
11830 }
11831
11832 void
11833 EditPositionDone(Boolean fakeRights)
11834 {
11835     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11836
11837     startedFromSetupPosition = TRUE;
11838     InitChessProgram(&first, FALSE);
11839     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11840       boards[0][EP_STATUS] = EP_NONE;
11841       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11842     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11843         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11844         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11845       } else boards[0][CASTLING][2] = NoRights;
11846     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11847         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11848         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11849       } else boards[0][CASTLING][5] = NoRights;
11850     }
11851     SendToProgram("force\n", &first);
11852     if (blackPlaysFirst) {
11853         strcpy(moveList[0], "");
11854         strcpy(parseList[0], "");
11855         currentMove = forwardMostMove = backwardMostMove = 1;
11856         CopyBoard(boards[1], boards[0]);
11857     } else {
11858         currentMove = forwardMostMove = backwardMostMove = 0;
11859     }
11860     SendBoard(&first, forwardMostMove);
11861     if (appData.debugMode) {
11862         fprintf(debugFP, "EditPosDone\n");
11863     }
11864     DisplayTitle("");
11865     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11866     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11867     gameMode = EditGame;
11868     ModeHighlight();
11869     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11870     ClearHighlights(); /* [AS] */
11871 }
11872
11873 /* Pause for `ms' milliseconds */
11874 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11875 void
11876 TimeDelay(ms)
11877      long ms;
11878 {
11879     TimeMark m1, m2;
11880
11881     GetTimeMark(&m1);
11882     do {
11883         GetTimeMark(&m2);
11884     } while (SubtractTimeMarks(&m2, &m1) < ms);
11885 }
11886
11887 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11888 void
11889 SendMultiLineToICS(buf)
11890      char *buf;
11891 {
11892     char temp[MSG_SIZ+1], *p;
11893     int len;
11894
11895     len = strlen(buf);
11896     if (len > MSG_SIZ)
11897       len = MSG_SIZ;
11898   
11899     strncpy(temp, buf, len);
11900     temp[len] = 0;
11901
11902     p = temp;
11903     while (*p) {
11904         if (*p == '\n' || *p == '\r')
11905           *p = ' ';
11906         ++p;
11907     }
11908
11909     strcat(temp, "\n");
11910     SendToICS(temp);
11911     SendToPlayer(temp, strlen(temp));
11912 }
11913
11914 void
11915 SetWhiteToPlayEvent()
11916 {
11917     if (gameMode == EditPosition) {
11918         blackPlaysFirst = FALSE;
11919         DisplayBothClocks();    /* works because currentMove is 0 */
11920     } else if (gameMode == IcsExamining) {
11921         SendToICS(ics_prefix);
11922         SendToICS("tomove white\n");
11923     }
11924 }
11925
11926 void
11927 SetBlackToPlayEvent()
11928 {
11929     if (gameMode == EditPosition) {
11930         blackPlaysFirst = TRUE;
11931         currentMove = 1;        /* kludge */
11932         DisplayBothClocks();
11933         currentMove = 0;
11934     } else if (gameMode == IcsExamining) {
11935         SendToICS(ics_prefix);
11936         SendToICS("tomove black\n");
11937     }
11938 }
11939
11940 void
11941 EditPositionMenuEvent(selection, x, y)
11942      ChessSquare selection;
11943      int x, y;
11944 {
11945     char buf[MSG_SIZ];
11946     ChessSquare piece = boards[0][y][x];
11947
11948     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11949
11950     switch (selection) {
11951       case ClearBoard:
11952         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11953             SendToICS(ics_prefix);
11954             SendToICS("bsetup clear\n");
11955         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11956             SendToICS(ics_prefix);
11957             SendToICS("clearboard\n");
11958         } else {
11959             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11960                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11961                 for (y = 0; y < BOARD_HEIGHT; y++) {
11962                     if (gameMode == IcsExamining) {
11963                         if (boards[currentMove][y][x] != EmptySquare) {
11964                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11965                                     AAA + x, ONE + y);
11966                             SendToICS(buf);
11967                         }
11968                     } else {
11969                         boards[0][y][x] = p;
11970                     }
11971                 }
11972             }
11973         }
11974         if (gameMode == EditPosition) {
11975             DrawPosition(FALSE, boards[0]);
11976         }
11977         break;
11978
11979       case WhitePlay:
11980         SetWhiteToPlayEvent();
11981         break;
11982
11983       case BlackPlay:
11984         SetBlackToPlayEvent();
11985         break;
11986
11987       case EmptySquare:
11988         if (gameMode == IcsExamining) {
11989             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11990             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11991             SendToICS(buf);
11992         } else {
11993             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11994                 if(x == BOARD_LEFT-2) {
11995                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11996                     boards[0][y][1] = 0;
11997                 } else
11998                 if(x == BOARD_RGHT+1) {
11999                     if(y >= gameInfo.holdingsSize) break;
12000                     boards[0][y][BOARD_WIDTH-2] = 0;
12001                 } else break;
12002             }
12003             boards[0][y][x] = EmptySquare;
12004             DrawPosition(FALSE, boards[0]);
12005         }
12006         break;
12007
12008       case PromotePiece:
12009         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12010            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12011             selection = (ChessSquare) (PROMOTED piece);
12012         } else if(piece == EmptySquare) selection = WhiteSilver;
12013         else selection = (ChessSquare)((int)piece - 1);
12014         goto defaultlabel;
12015
12016       case DemotePiece:
12017         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12018            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12019             selection = (ChessSquare) (DEMOTED piece);
12020         } else if(piece == EmptySquare) selection = BlackSilver;
12021         else selection = (ChessSquare)((int)piece + 1);       
12022         goto defaultlabel;
12023
12024       case WhiteQueen:
12025       case BlackQueen:
12026         if(gameInfo.variant == VariantShatranj ||
12027            gameInfo.variant == VariantXiangqi  ||
12028            gameInfo.variant == VariantCourier  ||
12029            gameInfo.variant == VariantMakruk     )
12030             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12031         goto defaultlabel;
12032
12033       case WhiteKing:
12034       case BlackKing:
12035         if(gameInfo.variant == VariantXiangqi)
12036             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12037         if(gameInfo.variant == VariantKnightmate)
12038             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12039       default:
12040         defaultlabel:
12041         if (gameMode == IcsExamining) {
12042             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12043             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12044                     PieceToChar(selection), AAA + x, ONE + y);
12045             SendToICS(buf);
12046         } else {
12047             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12048                 int n;
12049                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12050                     n = PieceToNumber(selection - BlackPawn);
12051                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12052                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12053                     boards[0][BOARD_HEIGHT-1-n][1]++;
12054                 } else
12055                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12056                     n = PieceToNumber(selection);
12057                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12058                     boards[0][n][BOARD_WIDTH-1] = selection;
12059                     boards[0][n][BOARD_WIDTH-2]++;
12060                 }
12061             } else
12062             boards[0][y][x] = selection;
12063             DrawPosition(TRUE, boards[0]);
12064         }
12065         break;
12066     }
12067 }
12068
12069
12070 void
12071 DropMenuEvent(selection, x, y)
12072      ChessSquare selection;
12073      int x, y;
12074 {
12075     ChessMove moveType;
12076
12077     switch (gameMode) {
12078       case IcsPlayingWhite:
12079       case MachinePlaysBlack:
12080         if (!WhiteOnMove(currentMove)) {
12081             DisplayMoveError(_("It is Black's turn"));
12082             return;
12083         }
12084         moveType = WhiteDrop;
12085         break;
12086       case IcsPlayingBlack:
12087       case MachinePlaysWhite:
12088         if (WhiteOnMove(currentMove)) {
12089             DisplayMoveError(_("It is White's turn"));
12090             return;
12091         }
12092         moveType = BlackDrop;
12093         break;
12094       case EditGame:
12095         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12096         break;
12097       default:
12098         return;
12099     }
12100
12101     if (moveType == BlackDrop && selection < BlackPawn) {
12102       selection = (ChessSquare) ((int) selection
12103                                  + (int) BlackPawn - (int) WhitePawn);
12104     }
12105     if (boards[currentMove][y][x] != EmptySquare) {
12106         DisplayMoveError(_("That square is occupied"));
12107         return;
12108     }
12109
12110     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12111 }
12112
12113 void
12114 AcceptEvent()
12115 {
12116     /* Accept a pending offer of any kind from opponent */
12117     
12118     if (appData.icsActive) {
12119         SendToICS(ics_prefix);
12120         SendToICS("accept\n");
12121     } else if (cmailMsgLoaded) {
12122         if (currentMove == cmailOldMove &&
12123             commentList[cmailOldMove] != NULL &&
12124             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12125                    "Black offers a draw" : "White offers a draw")) {
12126             TruncateGame();
12127             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12128             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12129         } else {
12130             DisplayError(_("There is no pending offer on this move"), 0);
12131             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12132         }
12133     } else {
12134         /* Not used for offers from chess program */
12135     }
12136 }
12137
12138 void
12139 DeclineEvent()
12140 {
12141     /* Decline a pending offer of any kind from opponent */
12142     
12143     if (appData.icsActive) {
12144         SendToICS(ics_prefix);
12145         SendToICS("decline\n");
12146     } else if (cmailMsgLoaded) {
12147         if (currentMove == cmailOldMove &&
12148             commentList[cmailOldMove] != NULL &&
12149             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12150                    "Black offers a draw" : "White offers a draw")) {
12151 #ifdef NOTDEF
12152             AppendComment(cmailOldMove, "Draw declined", TRUE);
12153             DisplayComment(cmailOldMove - 1, "Draw declined");
12154 #endif /*NOTDEF*/
12155         } else {
12156             DisplayError(_("There is no pending offer on this move"), 0);
12157         }
12158     } else {
12159         /* Not used for offers from chess program */
12160     }
12161 }
12162
12163 void
12164 RematchEvent()
12165 {
12166     /* Issue ICS rematch command */
12167     if (appData.icsActive) {
12168         SendToICS(ics_prefix);
12169         SendToICS("rematch\n");
12170     }
12171 }
12172
12173 void
12174 CallFlagEvent()
12175 {
12176     /* Call your opponent's flag (claim a win on time) */
12177     if (appData.icsActive) {
12178         SendToICS(ics_prefix);
12179         SendToICS("flag\n");
12180     } else {
12181         switch (gameMode) {
12182           default:
12183             return;
12184           case MachinePlaysWhite:
12185             if (whiteFlag) {
12186                 if (blackFlag)
12187                   GameEnds(GameIsDrawn, "Both players ran out of time",
12188                            GE_PLAYER);
12189                 else
12190                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12191             } else {
12192                 DisplayError(_("Your opponent is not out of time"), 0);
12193             }
12194             break;
12195           case MachinePlaysBlack:
12196             if (blackFlag) {
12197                 if (whiteFlag)
12198                   GameEnds(GameIsDrawn, "Both players ran out of time",
12199                            GE_PLAYER);
12200                 else
12201                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12202             } else {
12203                 DisplayError(_("Your opponent is not out of time"), 0);
12204             }
12205             break;
12206         }
12207     }
12208 }
12209
12210 void
12211 DrawEvent()
12212 {
12213     /* Offer draw or accept pending draw offer from opponent */
12214     
12215     if (appData.icsActive) {
12216         /* Note: tournament rules require draw offers to be
12217            made after you make your move but before you punch
12218            your clock.  Currently ICS doesn't let you do that;
12219            instead, you immediately punch your clock after making
12220            a move, but you can offer a draw at any time. */
12221         
12222         SendToICS(ics_prefix);
12223         SendToICS("draw\n");
12224         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12225     } else if (cmailMsgLoaded) {
12226         if (currentMove == cmailOldMove &&
12227             commentList[cmailOldMove] != NULL &&
12228             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12229                    "Black offers a draw" : "White offers a draw")) {
12230             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12231             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12232         } else if (currentMove == cmailOldMove + 1) {
12233             char *offer = WhiteOnMove(cmailOldMove) ?
12234               "White offers a draw" : "Black offers a draw";
12235             AppendComment(currentMove, offer, TRUE);
12236             DisplayComment(currentMove - 1, offer);
12237             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12238         } else {
12239             DisplayError(_("You must make your move before offering a draw"), 0);
12240             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12241         }
12242     } else if (first.offeredDraw) {
12243         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12244     } else {
12245         if (first.sendDrawOffers) {
12246             SendToProgram("draw\n", &first);
12247             userOfferedDraw = TRUE;
12248         }
12249     }
12250 }
12251
12252 void
12253 AdjournEvent()
12254 {
12255     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12256     
12257     if (appData.icsActive) {
12258         SendToICS(ics_prefix);
12259         SendToICS("adjourn\n");
12260     } else {
12261         /* Currently GNU Chess doesn't offer or accept Adjourns */
12262     }
12263 }
12264
12265
12266 void
12267 AbortEvent()
12268 {
12269     /* Offer Abort or accept pending Abort offer from opponent */
12270     
12271     if (appData.icsActive) {
12272         SendToICS(ics_prefix);
12273         SendToICS("abort\n");
12274     } else {
12275         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12276     }
12277 }
12278
12279 void
12280 ResignEvent()
12281 {
12282     /* Resign.  You can do this even if it's not your turn. */
12283     
12284     if (appData.icsActive) {
12285         SendToICS(ics_prefix);
12286         SendToICS("resign\n");
12287     } else {
12288         switch (gameMode) {
12289           case MachinePlaysWhite:
12290             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12291             break;
12292           case MachinePlaysBlack:
12293             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12294             break;
12295           case EditGame:
12296             if (cmailMsgLoaded) {
12297                 TruncateGame();
12298                 if (WhiteOnMove(cmailOldMove)) {
12299                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12300                 } else {
12301                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12302                 }
12303                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12304             }
12305             break;
12306           default:
12307             break;
12308         }
12309     }
12310 }
12311
12312
12313 void
12314 StopObservingEvent()
12315 {
12316     /* Stop observing current games */
12317     SendToICS(ics_prefix);
12318     SendToICS("unobserve\n");
12319 }
12320
12321 void
12322 StopExaminingEvent()
12323 {
12324     /* Stop observing current game */
12325     SendToICS(ics_prefix);
12326     SendToICS("unexamine\n");
12327 }
12328
12329 void
12330 ForwardInner(target)
12331      int target;
12332 {
12333     int limit;
12334
12335     if (appData.debugMode)
12336         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12337                 target, currentMove, forwardMostMove);
12338
12339     if (gameMode == EditPosition)
12340       return;
12341
12342     if (gameMode == PlayFromGameFile && !pausing)
12343       PauseEvent();
12344     
12345     if (gameMode == IcsExamining && pausing)
12346       limit = pauseExamForwardMostMove;
12347     else
12348       limit = forwardMostMove;
12349     
12350     if (target > limit) target = limit;
12351
12352     if (target > 0 && moveList[target - 1][0]) {
12353         int fromX, fromY, toX, toY;
12354         toX = moveList[target - 1][2] - AAA;
12355         toY = moveList[target - 1][3] - ONE;
12356         if (moveList[target - 1][1] == '@') {
12357             if (appData.highlightLastMove) {
12358                 SetHighlights(-1, -1, toX, toY);
12359             }
12360         } else {
12361             fromX = moveList[target - 1][0] - AAA;
12362             fromY = moveList[target - 1][1] - ONE;
12363             if (target == currentMove + 1) {
12364                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12365             }
12366             if (appData.highlightLastMove) {
12367                 SetHighlights(fromX, fromY, toX, toY);
12368             }
12369         }
12370     }
12371     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12372         gameMode == Training || gameMode == PlayFromGameFile || 
12373         gameMode == AnalyzeFile) {
12374         while (currentMove < target) {
12375             SendMoveToProgram(currentMove++, &first);
12376         }
12377     } else {
12378         currentMove = target;
12379     }
12380     
12381     if (gameMode == EditGame || gameMode == EndOfGame) {
12382         whiteTimeRemaining = timeRemaining[0][currentMove];
12383         blackTimeRemaining = timeRemaining[1][currentMove];
12384     }
12385     DisplayBothClocks();
12386     DisplayMove(currentMove - 1);
12387     DrawPosition(FALSE, boards[currentMove]);
12388     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12389     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12390         DisplayComment(currentMove - 1, commentList[currentMove]);
12391     }
12392 }
12393
12394
12395 void
12396 ForwardEvent()
12397 {
12398     if (gameMode == IcsExamining && !pausing) {
12399         SendToICS(ics_prefix);
12400         SendToICS("forward\n");
12401     } else {
12402         ForwardInner(currentMove + 1);
12403     }
12404 }
12405
12406 void
12407 ToEndEvent()
12408 {
12409     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12410         /* to optimze, we temporarily turn off analysis mode while we feed
12411          * the remaining moves to the engine. Otherwise we get analysis output
12412          * after each move.
12413          */ 
12414         if (first.analysisSupport) {
12415           SendToProgram("exit\nforce\n", &first);
12416           first.analyzing = FALSE;
12417         }
12418     }
12419         
12420     if (gameMode == IcsExamining && !pausing) {
12421         SendToICS(ics_prefix);
12422         SendToICS("forward 999999\n");
12423     } else {
12424         ForwardInner(forwardMostMove);
12425     }
12426
12427     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12428         /* we have fed all the moves, so reactivate analysis mode */
12429         SendToProgram("analyze\n", &first);
12430         first.analyzing = TRUE;
12431         /*first.maybeThinking = TRUE;*/
12432         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12433     }
12434 }
12435
12436 void
12437 BackwardInner(target)
12438      int target;
12439 {
12440     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12441
12442     if (appData.debugMode)
12443         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12444                 target, currentMove, forwardMostMove);
12445
12446     if (gameMode == EditPosition) return;
12447     if (currentMove <= backwardMostMove) {
12448         ClearHighlights();
12449         DrawPosition(full_redraw, boards[currentMove]);
12450         return;
12451     }
12452     if (gameMode == PlayFromGameFile && !pausing)
12453       PauseEvent();
12454     
12455     if (moveList[target][0]) {
12456         int fromX, fromY, toX, toY;
12457         toX = moveList[target][2] - AAA;
12458         toY = moveList[target][3] - ONE;
12459         if (moveList[target][1] == '@') {
12460             if (appData.highlightLastMove) {
12461                 SetHighlights(-1, -1, toX, toY);
12462             }
12463         } else {
12464             fromX = moveList[target][0] - AAA;
12465             fromY = moveList[target][1] - ONE;
12466             if (target == currentMove - 1) {
12467                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12468             }
12469             if (appData.highlightLastMove) {
12470                 SetHighlights(fromX, fromY, toX, toY);
12471             }
12472         }
12473     }
12474     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12475         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12476         while (currentMove > target) {
12477             SendToProgram("undo\n", &first);
12478             currentMove--;
12479         }
12480     } else {
12481         currentMove = target;
12482     }
12483     
12484     if (gameMode == EditGame || gameMode == EndOfGame) {
12485         whiteTimeRemaining = timeRemaining[0][currentMove];
12486         blackTimeRemaining = timeRemaining[1][currentMove];
12487     }
12488     DisplayBothClocks();
12489     DisplayMove(currentMove - 1);
12490     DrawPosition(full_redraw, boards[currentMove]);
12491     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12492     // [HGM] PV info: routine tests if comment empty
12493     DisplayComment(currentMove - 1, commentList[currentMove]);
12494 }
12495
12496 void
12497 BackwardEvent()
12498 {
12499     if (gameMode == IcsExamining && !pausing) {
12500         SendToICS(ics_prefix);
12501         SendToICS("backward\n");
12502     } else {
12503         BackwardInner(currentMove - 1);
12504     }
12505 }
12506
12507 void
12508 ToStartEvent()
12509 {
12510     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12511         /* to optimize, we temporarily turn off analysis mode while we undo
12512          * all the moves. Otherwise we get analysis output after each undo.
12513          */ 
12514         if (first.analysisSupport) {
12515           SendToProgram("exit\nforce\n", &first);
12516           first.analyzing = FALSE;
12517         }
12518     }
12519
12520     if (gameMode == IcsExamining && !pausing) {
12521         SendToICS(ics_prefix);
12522         SendToICS("backward 999999\n");
12523     } else {
12524         BackwardInner(backwardMostMove);
12525     }
12526
12527     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12528         /* we have fed all the moves, so reactivate analysis mode */
12529         SendToProgram("analyze\n", &first);
12530         first.analyzing = TRUE;
12531         /*first.maybeThinking = TRUE;*/
12532         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12533     }
12534 }
12535
12536 void
12537 ToNrEvent(int to)
12538 {
12539   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12540   if (to >= forwardMostMove) to = forwardMostMove;
12541   if (to <= backwardMostMove) to = backwardMostMove;
12542   if (to < currentMove) {
12543     BackwardInner(to);
12544   } else {
12545     ForwardInner(to);
12546   }
12547 }
12548
12549 void
12550 RevertEvent()
12551 {
12552     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12553         return;
12554     }
12555     if (gameMode != IcsExamining) {
12556         DisplayError(_("You are not examining a game"), 0);
12557         return;
12558     }
12559     if (pausing) {
12560         DisplayError(_("You can't revert while pausing"), 0);
12561         return;
12562     }
12563     SendToICS(ics_prefix);
12564     SendToICS("revert\n");
12565 }
12566
12567 void
12568 RetractMoveEvent()
12569 {
12570     switch (gameMode) {
12571       case MachinePlaysWhite:
12572       case MachinePlaysBlack:
12573         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12574             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12575             return;
12576         }
12577         if (forwardMostMove < 2) return;
12578         currentMove = forwardMostMove = forwardMostMove - 2;
12579         whiteTimeRemaining = timeRemaining[0][currentMove];
12580         blackTimeRemaining = timeRemaining[1][currentMove];
12581         DisplayBothClocks();
12582         DisplayMove(currentMove - 1);
12583         ClearHighlights();/*!! could figure this out*/
12584         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12585         SendToProgram("remove\n", &first);
12586         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12587         break;
12588
12589       case BeginningOfGame:
12590       default:
12591         break;
12592
12593       case IcsPlayingWhite:
12594       case IcsPlayingBlack:
12595         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12596             SendToICS(ics_prefix);
12597             SendToICS("takeback 2\n");
12598         } else {
12599             SendToICS(ics_prefix);
12600             SendToICS("takeback 1\n");
12601         }
12602         break;
12603     }
12604 }
12605
12606 void
12607 MoveNowEvent()
12608 {
12609     ChessProgramState *cps;
12610
12611     switch (gameMode) {
12612       case MachinePlaysWhite:
12613         if (!WhiteOnMove(forwardMostMove)) {
12614             DisplayError(_("It is your turn"), 0);
12615             return;
12616         }
12617         cps = &first;
12618         break;
12619       case MachinePlaysBlack:
12620         if (WhiteOnMove(forwardMostMove)) {
12621             DisplayError(_("It is your turn"), 0);
12622             return;
12623         }
12624         cps = &first;
12625         break;
12626       case TwoMachinesPlay:
12627         if (WhiteOnMove(forwardMostMove) ==
12628             (first.twoMachinesColor[0] == 'w')) {
12629             cps = &first;
12630         } else {
12631             cps = &second;
12632         }
12633         break;
12634       case BeginningOfGame:
12635       default:
12636         return;
12637     }
12638     SendToProgram("?\n", cps);
12639 }
12640
12641 void
12642 TruncateGameEvent()
12643 {
12644     EditGameEvent();
12645     if (gameMode != EditGame) return;
12646     TruncateGame();
12647 }
12648
12649 void
12650 TruncateGame()
12651 {
12652     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12653     if (forwardMostMove > currentMove) {
12654         if (gameInfo.resultDetails != NULL) {
12655             free(gameInfo.resultDetails);
12656             gameInfo.resultDetails = NULL;
12657             gameInfo.result = GameUnfinished;
12658         }
12659         forwardMostMove = currentMove;
12660         HistorySet(parseList, backwardMostMove, forwardMostMove,
12661                    currentMove-1);
12662     }
12663 }
12664
12665 void
12666 HintEvent()
12667 {
12668     if (appData.noChessProgram) return;
12669     switch (gameMode) {
12670       case MachinePlaysWhite:
12671         if (WhiteOnMove(forwardMostMove)) {
12672             DisplayError(_("Wait until your turn"), 0);
12673             return;
12674         }
12675         break;
12676       case BeginningOfGame:
12677       case MachinePlaysBlack:
12678         if (!WhiteOnMove(forwardMostMove)) {
12679             DisplayError(_("Wait until your turn"), 0);
12680             return;
12681         }
12682         break;
12683       default:
12684         DisplayError(_("No hint available"), 0);
12685         return;
12686     }
12687     SendToProgram("hint\n", &first);
12688     hintRequested = TRUE;
12689 }
12690
12691 void
12692 BookEvent()
12693 {
12694     if (appData.noChessProgram) return;
12695     switch (gameMode) {
12696       case MachinePlaysWhite:
12697         if (WhiteOnMove(forwardMostMove)) {
12698             DisplayError(_("Wait until your turn"), 0);
12699             return;
12700         }
12701         break;
12702       case BeginningOfGame:
12703       case MachinePlaysBlack:
12704         if (!WhiteOnMove(forwardMostMove)) {
12705             DisplayError(_("Wait until your turn"), 0);
12706             return;
12707         }
12708         break;
12709       case EditPosition:
12710         EditPositionDone(TRUE);
12711         break;
12712       case TwoMachinesPlay:
12713         return;
12714       default:
12715         break;
12716     }
12717     SendToProgram("bk\n", &first);
12718     bookOutput[0] = NULLCHAR;
12719     bookRequested = TRUE;
12720 }
12721
12722 void
12723 AboutGameEvent()
12724 {
12725     char *tags = PGNTags(&gameInfo);
12726     TagsPopUp(tags, CmailMsg());
12727     free(tags);
12728 }
12729
12730 /* end button procedures */
12731
12732 void
12733 PrintPosition(fp, move)
12734      FILE *fp;
12735      int move;
12736 {
12737     int i, j;
12738     
12739     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12740         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12741             char c = PieceToChar(boards[move][i][j]);
12742             fputc(c == 'x' ? '.' : c, fp);
12743             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12744         }
12745     }
12746     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12747       fprintf(fp, "white to play\n");
12748     else
12749       fprintf(fp, "black to play\n");
12750 }
12751
12752 void
12753 PrintOpponents(fp)
12754      FILE *fp;
12755 {
12756     if (gameInfo.white != NULL) {
12757         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12758     } else {
12759         fprintf(fp, "\n");
12760     }
12761 }
12762
12763 /* Find last component of program's own name, using some heuristics */
12764 void
12765 TidyProgramName(prog, host, buf)
12766      char *prog, *host, buf[MSG_SIZ];
12767 {
12768     char *p, *q;
12769     int local = (strcmp(host, "localhost") == 0);
12770     while (!local && (p = strchr(prog, ';')) != NULL) {
12771         p++;
12772         while (*p == ' ') p++;
12773         prog = p;
12774     }
12775     if (*prog == '"' || *prog == '\'') {
12776         q = strchr(prog + 1, *prog);
12777     } else {
12778         q = strchr(prog, ' ');
12779     }
12780     if (q == NULL) q = prog + strlen(prog);
12781     p = q;
12782     while (p >= prog && *p != '/' && *p != '\\') p--;
12783     p++;
12784     if(p == prog && *p == '"') p++;
12785     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12786     memcpy(buf, p, q - p);
12787     buf[q - p] = NULLCHAR;
12788     if (!local) {
12789         strcat(buf, "@");
12790         strcat(buf, host);
12791     }
12792 }
12793
12794 char *
12795 TimeControlTagValue()
12796 {
12797     char buf[MSG_SIZ];
12798     if (!appData.clockMode) {
12799         strcpy(buf, "-");
12800     } else if (movesPerSession > 0) {
12801         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12802     } else if (timeIncrement == 0) {
12803         sprintf(buf, "%ld", timeControl/1000);
12804     } else {
12805         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12806     }
12807     return StrSave(buf);
12808 }
12809
12810 void
12811 SetGameInfo()
12812 {
12813     /* This routine is used only for certain modes */
12814     VariantClass v = gameInfo.variant;
12815     ChessMove r = GameUnfinished;
12816     char *p = NULL;
12817
12818     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12819         r = gameInfo.result; 
12820         p = gameInfo.resultDetails; 
12821         gameInfo.resultDetails = NULL;
12822     }
12823     ClearGameInfo(&gameInfo);
12824     gameInfo.variant = v;
12825
12826     switch (gameMode) {
12827       case MachinePlaysWhite:
12828         gameInfo.event = StrSave( appData.pgnEventHeader );
12829         gameInfo.site = StrSave(HostName());
12830         gameInfo.date = PGNDate();
12831         gameInfo.round = StrSave("-");
12832         gameInfo.white = StrSave(first.tidy);
12833         gameInfo.black = StrSave(UserName());
12834         gameInfo.timeControl = TimeControlTagValue();
12835         break;
12836
12837       case MachinePlaysBlack:
12838         gameInfo.event = StrSave( appData.pgnEventHeader );
12839         gameInfo.site = StrSave(HostName());
12840         gameInfo.date = PGNDate();
12841         gameInfo.round = StrSave("-");
12842         gameInfo.white = StrSave(UserName());
12843         gameInfo.black = StrSave(first.tidy);
12844         gameInfo.timeControl = TimeControlTagValue();
12845         break;
12846
12847       case TwoMachinesPlay:
12848         gameInfo.event = StrSave( appData.pgnEventHeader );
12849         gameInfo.site = StrSave(HostName());
12850         gameInfo.date = PGNDate();
12851         if (matchGame > 0) {
12852             char buf[MSG_SIZ];
12853             sprintf(buf, "%d", matchGame);
12854             gameInfo.round = StrSave(buf);
12855         } else {
12856             gameInfo.round = StrSave("-");
12857         }
12858         if (first.twoMachinesColor[0] == 'w') {
12859             gameInfo.white = StrSave(first.tidy);
12860             gameInfo.black = StrSave(second.tidy);
12861         } else {
12862             gameInfo.white = StrSave(second.tidy);
12863             gameInfo.black = StrSave(first.tidy);
12864         }
12865         gameInfo.timeControl = TimeControlTagValue();
12866         break;
12867
12868       case EditGame:
12869         gameInfo.event = StrSave("Edited game");
12870         gameInfo.site = StrSave(HostName());
12871         gameInfo.date = PGNDate();
12872         gameInfo.round = StrSave("-");
12873         gameInfo.white = StrSave("-");
12874         gameInfo.black = StrSave("-");
12875         gameInfo.result = r;
12876         gameInfo.resultDetails = p;
12877         break;
12878
12879       case EditPosition:
12880         gameInfo.event = StrSave("Edited position");
12881         gameInfo.site = StrSave(HostName());
12882         gameInfo.date = PGNDate();
12883         gameInfo.round = StrSave("-");
12884         gameInfo.white = StrSave("-");
12885         gameInfo.black = StrSave("-");
12886         break;
12887
12888       case IcsPlayingWhite:
12889       case IcsPlayingBlack:
12890       case IcsObserving:
12891       case IcsExamining:
12892         break;
12893
12894       case PlayFromGameFile:
12895         gameInfo.event = StrSave("Game from non-PGN file");
12896         gameInfo.site = StrSave(HostName());
12897         gameInfo.date = PGNDate();
12898         gameInfo.round = StrSave("-");
12899         gameInfo.white = StrSave("?");
12900         gameInfo.black = StrSave("?");
12901         break;
12902
12903       default:
12904         break;
12905     }
12906 }
12907
12908 void
12909 ReplaceComment(index, text)
12910      int index;
12911      char *text;
12912 {
12913     int len;
12914
12915     while (*text == '\n') text++;
12916     len = strlen(text);
12917     while (len > 0 && text[len - 1] == '\n') len--;
12918
12919     if (commentList[index] != NULL)
12920       free(commentList[index]);
12921
12922     if (len == 0) {
12923         commentList[index] = NULL;
12924         return;
12925     }
12926   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12927       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12928       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12929     commentList[index] = (char *) malloc(len + 2);
12930     strncpy(commentList[index], text, len);
12931     commentList[index][len] = '\n';
12932     commentList[index][len + 1] = NULLCHAR;
12933   } else { 
12934     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12935     char *p;
12936     commentList[index] = (char *) malloc(len + 6);
12937     strcpy(commentList[index], "{\n");
12938     strncpy(commentList[index]+2, text, len);
12939     commentList[index][len+2] = NULLCHAR;
12940     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12941     strcat(commentList[index], "\n}\n");
12942   }
12943 }
12944
12945 void
12946 CrushCRs(text)
12947      char *text;
12948 {
12949   char *p = text;
12950   char *q = text;
12951   char ch;
12952
12953   do {
12954     ch = *p++;
12955     if (ch == '\r') continue;
12956     *q++ = ch;
12957   } while (ch != '\0');
12958 }
12959
12960 void
12961 AppendComment(index, text, addBraces)
12962      int index;
12963      char *text;
12964      Boolean addBraces; // [HGM] braces: tells if we should add {}
12965 {
12966     int oldlen, len;
12967     char *old;
12968
12969 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12970     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12971
12972     CrushCRs(text);
12973     while (*text == '\n') text++;
12974     len = strlen(text);
12975     while (len > 0 && text[len - 1] == '\n') len--;
12976
12977     if (len == 0) return;
12978
12979     if (commentList[index] != NULL) {
12980         old = commentList[index];
12981         oldlen = strlen(old);
12982         while(commentList[index][oldlen-1] ==  '\n')
12983           commentList[index][--oldlen] = NULLCHAR;
12984         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12985         strcpy(commentList[index], old);
12986         free(old);
12987         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12988         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12989           if(addBraces) addBraces = FALSE; else { text++; len--; }
12990           while (*text == '\n') { text++; len--; }
12991           commentList[index][--oldlen] = NULLCHAR;
12992       }
12993         if(addBraces) strcat(commentList[index], "\n{\n");
12994         else          strcat(commentList[index], "\n");
12995         strcat(commentList[index], text);
12996         if(addBraces) strcat(commentList[index], "\n}\n");
12997         else          strcat(commentList[index], "\n");
12998     } else {
12999         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13000         if(addBraces)
13001              strcpy(commentList[index], "{\n");
13002         else commentList[index][0] = NULLCHAR;
13003         strcat(commentList[index], text);
13004         strcat(commentList[index], "\n");
13005         if(addBraces) strcat(commentList[index], "}\n");
13006     }
13007 }
13008
13009 static char * FindStr( char * text, char * sub_text )
13010 {
13011     char * result = strstr( text, sub_text );
13012
13013     if( result != NULL ) {
13014         result += strlen( sub_text );
13015     }
13016
13017     return result;
13018 }
13019
13020 /* [AS] Try to extract PV info from PGN comment */
13021 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13022 char *GetInfoFromComment( int index, char * text )
13023 {
13024     char * sep = text;
13025
13026     if( text != NULL && index > 0 ) {
13027         int score = 0;
13028         int depth = 0;
13029         int time = -1, sec = 0, deci;
13030         char * s_eval = FindStr( text, "[%eval " );
13031         char * s_emt = FindStr( text, "[%emt " );
13032
13033         if( s_eval != NULL || s_emt != NULL ) {
13034             /* New style */
13035             char delim;
13036
13037             if( s_eval != NULL ) {
13038                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13039                     return text;
13040                 }
13041
13042                 if( delim != ']' ) {
13043                     return text;
13044                 }
13045             }
13046
13047             if( s_emt != NULL ) {
13048             }
13049                 return text;
13050         }
13051         else {
13052             /* We expect something like: [+|-]nnn.nn/dd */
13053             int score_lo = 0;
13054
13055             if(*text != '{') return text; // [HGM] braces: must be normal comment
13056
13057             sep = strchr( text, '/' );
13058             if( sep == NULL || sep < (text+4) ) {
13059                 return text;
13060             }
13061
13062             time = -1; sec = -1; deci = -1;
13063             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13064                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13065                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13066                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13067                 return text;
13068             }
13069
13070             if( score_lo < 0 || score_lo >= 100 ) {
13071                 return text;
13072             }
13073
13074             if(sec >= 0) time = 600*time + 10*sec; else
13075             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13076
13077             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13078
13079             /* [HGM] PV time: now locate end of PV info */
13080             while( *++sep >= '0' && *sep <= '9'); // strip depth
13081             if(time >= 0)
13082             while( *++sep >= '0' && *sep <= '9'); // strip time
13083             if(sec >= 0)
13084             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13085             if(deci >= 0)
13086             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13087             while(*sep == ' ') sep++;
13088         }
13089
13090         if( depth <= 0 ) {
13091             return text;
13092         }
13093
13094         if( time < 0 ) {
13095             time = -1;
13096         }
13097
13098         pvInfoList[index-1].depth = depth;
13099         pvInfoList[index-1].score = score;
13100         pvInfoList[index-1].time  = 10*time; // centi-sec
13101         if(*sep == '}') *sep = 0; else *--sep = '{';
13102     }
13103     return sep;
13104 }
13105
13106 void
13107 SendToProgram(message, cps)
13108      char *message;
13109      ChessProgramState *cps;
13110 {
13111     int count, outCount, error;
13112     char buf[MSG_SIZ];
13113
13114     if (cps->pr == NULL) return;
13115     Attention(cps);
13116     
13117     if (appData.debugMode) {
13118         TimeMark now;
13119         GetTimeMark(&now);
13120         fprintf(debugFP, "%ld >%-6s: %s", 
13121                 SubtractTimeMarks(&now, &programStartTime),
13122                 cps->which, message);
13123     }
13124     
13125     count = strlen(message);
13126     outCount = OutputToProcess(cps->pr, message, count, &error);
13127     if (outCount < count && !exiting 
13128                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13129         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13130         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13131             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13132                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13133                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13134             } else {
13135                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13136             }
13137             gameInfo.resultDetails = StrSave(buf);
13138         }
13139         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13140     }
13141 }
13142
13143 void
13144 ReceiveFromProgram(isr, closure, message, count, error)
13145      InputSourceRef isr;
13146      VOIDSTAR closure;
13147      char *message;
13148      int count;
13149      int error;
13150 {
13151     char *end_str;
13152     char buf[MSG_SIZ];
13153     ChessProgramState *cps = (ChessProgramState *)closure;
13154
13155     if (isr != cps->isr) return; /* Killed intentionally */
13156     if (count <= 0) {
13157         if (count == 0) {
13158             sprintf(buf,
13159                     _("Error: %s chess program (%s) exited unexpectedly"),
13160                     cps->which, cps->program);
13161         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13162                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13163                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13164                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13165                 } else {
13166                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13167                 }
13168                 gameInfo.resultDetails = StrSave(buf);
13169             }
13170             RemoveInputSource(cps->isr);
13171             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13172         } else {
13173             sprintf(buf,
13174                     _("Error reading from %s chess program (%s)"),
13175                     cps->which, cps->program);
13176             RemoveInputSource(cps->isr);
13177
13178             /* [AS] Program is misbehaving badly... kill it */
13179             if( count == -2 ) {
13180                 DestroyChildProcess( cps->pr, 9 );
13181                 cps->pr = NoProc;
13182             }
13183
13184             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13185         }
13186         return;
13187     }
13188     
13189     if ((end_str = strchr(message, '\r')) != NULL)
13190       *end_str = NULLCHAR;
13191     if ((end_str = strchr(message, '\n')) != NULL)
13192       *end_str = NULLCHAR;
13193     
13194     if (appData.debugMode) {
13195         TimeMark now; int print = 1;
13196         char *quote = ""; char c; int i;
13197
13198         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13199                 char start = message[0];
13200                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13201                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13202                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13203                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13204                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13205                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13206                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13207                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13208                         { quote = "# "; print = (appData.engineComments == 2); }
13209                 message[0] = start; // restore original message
13210         }
13211         if(print) {
13212                 GetTimeMark(&now);
13213                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13214                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13215                         quote,
13216                         message);
13217         }
13218     }
13219
13220     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13221     if (appData.icsEngineAnalyze) {
13222         if (strstr(message, "whisper") != NULL ||
13223              strstr(message, "kibitz") != NULL || 
13224             strstr(message, "tellics") != NULL) return;
13225     }
13226
13227     HandleMachineMove(message, cps);
13228 }
13229
13230
13231 void
13232 SendTimeControl(cps, mps, tc, inc, sd, st)
13233      ChessProgramState *cps;
13234      int mps, inc, sd, st;
13235      long tc;
13236 {
13237     char buf[MSG_SIZ];
13238     int seconds;
13239
13240     if( timeControl_2 > 0 ) {
13241         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13242             tc = timeControl_2;
13243         }
13244     }
13245     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13246     inc /= cps->timeOdds;
13247     st  /= cps->timeOdds;
13248
13249     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13250
13251     if (st > 0) {
13252       /* Set exact time per move, normally using st command */
13253       if (cps->stKludge) {
13254         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13255         seconds = st % 60;
13256         if (seconds == 0) {
13257           sprintf(buf, "level 1 %d\n", st/60);
13258         } else {
13259           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13260         }
13261       } else {
13262         sprintf(buf, "st %d\n", st);
13263       }
13264     } else {
13265       /* Set conventional or incremental time control, using level command */
13266       if (seconds == 0) {
13267         /* Note old gnuchess bug -- minutes:seconds used to not work.
13268            Fixed in later versions, but still avoid :seconds
13269            when seconds is 0. */
13270         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13271       } else {
13272         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13273                 seconds, inc/1000);
13274       }
13275     }
13276     SendToProgram(buf, cps);
13277
13278     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13279     /* Orthogonally, limit search to given depth */
13280     if (sd > 0) {
13281       if (cps->sdKludge) {
13282         sprintf(buf, "depth\n%d\n", sd);
13283       } else {
13284         sprintf(buf, "sd %d\n", sd);
13285       }
13286       SendToProgram(buf, cps);
13287     }
13288
13289     if(cps->nps > 0) { /* [HGM] nps */
13290         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13291         else {
13292                 sprintf(buf, "nps %d\n", cps->nps);
13293               SendToProgram(buf, cps);
13294         }
13295     }
13296 }
13297
13298 ChessProgramState *WhitePlayer()
13299 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13300 {
13301     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13302        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13303         return &second;
13304     return &first;
13305 }
13306
13307 void
13308 SendTimeRemaining(cps, machineWhite)
13309      ChessProgramState *cps;
13310      int /*boolean*/ machineWhite;
13311 {
13312     char message[MSG_SIZ];
13313     long time, otime;
13314
13315     /* Note: this routine must be called when the clocks are stopped
13316        or when they have *just* been set or switched; otherwise
13317        it will be off by the time since the current tick started.
13318     */
13319     if (machineWhite) {
13320         time = whiteTimeRemaining / 10;
13321         otime = blackTimeRemaining / 10;
13322     } else {
13323         time = blackTimeRemaining / 10;
13324         otime = whiteTimeRemaining / 10;
13325     }
13326     /* [HGM] translate opponent's time by time-odds factor */
13327     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13328     if (appData.debugMode) {
13329         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13330     }
13331
13332     if (time <= 0) time = 1;
13333     if (otime <= 0) otime = 1;
13334     
13335     sprintf(message, "time %ld\n", time);
13336     SendToProgram(message, cps);
13337
13338     sprintf(message, "otim %ld\n", otime);
13339     SendToProgram(message, cps);
13340 }
13341
13342 int
13343 BoolFeature(p, name, loc, cps)
13344      char **p;
13345      char *name;
13346      int *loc;
13347      ChessProgramState *cps;
13348 {
13349   char buf[MSG_SIZ];
13350   int len = strlen(name);
13351   int val;
13352   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13353     (*p) += len + 1;
13354     sscanf(*p, "%d", &val);
13355     *loc = (val != 0);
13356     while (**p && **p != ' ') (*p)++;
13357     sprintf(buf, "accepted %s\n", name);
13358     SendToProgram(buf, cps);
13359     return TRUE;
13360   }
13361   return FALSE;
13362 }
13363
13364 int
13365 IntFeature(p, name, loc, cps)
13366      char **p;
13367      char *name;
13368      int *loc;
13369      ChessProgramState *cps;
13370 {
13371   char buf[MSG_SIZ];
13372   int len = strlen(name);
13373   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13374     (*p) += len + 1;
13375     sscanf(*p, "%d", loc);
13376     while (**p && **p != ' ') (*p)++;
13377     sprintf(buf, "accepted %s\n", name);
13378     SendToProgram(buf, cps);
13379     return TRUE;
13380   }
13381   return FALSE;
13382 }
13383
13384 int
13385 StringFeature(p, name, loc, cps)
13386      char **p;
13387      char *name;
13388      char loc[];
13389      ChessProgramState *cps;
13390 {
13391   char buf[MSG_SIZ];
13392   int len = strlen(name);
13393   if (strncmp((*p), name, len) == 0
13394       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13395     (*p) += len + 2;
13396     sscanf(*p, "%[^\"]", loc);
13397     while (**p && **p != '\"') (*p)++;
13398     if (**p == '\"') (*p)++;
13399     sprintf(buf, "accepted %s\n", name);
13400     SendToProgram(buf, cps);
13401     return TRUE;
13402   }
13403   return FALSE;
13404 }
13405
13406 int 
13407 ParseOption(Option *opt, ChessProgramState *cps)
13408 // [HGM] options: process the string that defines an engine option, and determine
13409 // name, type, default value, and allowed value range
13410 {
13411         char *p, *q, buf[MSG_SIZ];
13412         int n, min = (-1)<<31, max = 1<<31, def;
13413
13414         if(p = strstr(opt->name, " -spin ")) {
13415             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13416             if(max < min) max = min; // enforce consistency
13417             if(def < min) def = min;
13418             if(def > max) def = max;
13419             opt->value = def;
13420             opt->min = min;
13421             opt->max = max;
13422             opt->type = Spin;
13423         } else if((p = strstr(opt->name, " -slider "))) {
13424             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13425             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13426             if(max < min) max = min; // enforce consistency
13427             if(def < min) def = min;
13428             if(def > max) def = max;
13429             opt->value = def;
13430             opt->min = min;
13431             opt->max = max;
13432             opt->type = Spin; // Slider;
13433         } else if((p = strstr(opt->name, " -string "))) {
13434             opt->textValue = p+9;
13435             opt->type = TextBox;
13436         } else if((p = strstr(opt->name, " -file "))) {
13437             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13438             opt->textValue = p+7;
13439             opt->type = TextBox; // FileName;
13440         } else if((p = strstr(opt->name, " -path "))) {
13441             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13442             opt->textValue = p+7;
13443             opt->type = TextBox; // PathName;
13444         } else if(p = strstr(opt->name, " -check ")) {
13445             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13446             opt->value = (def != 0);
13447             opt->type = CheckBox;
13448         } else if(p = strstr(opt->name, " -combo ")) {
13449             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13450             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13451             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13452             opt->value = n = 0;
13453             while(q = StrStr(q, " /// ")) {
13454                 n++; *q = 0;    // count choices, and null-terminate each of them
13455                 q += 5;
13456                 if(*q == '*') { // remember default, which is marked with * prefix
13457                     q++;
13458                     opt->value = n;
13459                 }
13460                 cps->comboList[cps->comboCnt++] = q;
13461             }
13462             cps->comboList[cps->comboCnt++] = NULL;
13463             opt->max = n + 1;
13464             opt->type = ComboBox;
13465         } else if(p = strstr(opt->name, " -button")) {
13466             opt->type = Button;
13467         } else if(p = strstr(opt->name, " -save")) {
13468             opt->type = SaveButton;
13469         } else return FALSE;
13470         *p = 0; // terminate option name
13471         // now look if the command-line options define a setting for this engine option.
13472         if(cps->optionSettings && cps->optionSettings[0])
13473             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13474         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13475                 sprintf(buf, "option %s", p);
13476                 if(p = strstr(buf, ",")) *p = 0;
13477                 strcat(buf, "\n");
13478                 SendToProgram(buf, cps);
13479         }
13480         return TRUE;
13481 }
13482
13483 void
13484 FeatureDone(cps, val)
13485      ChessProgramState* cps;
13486      int val;
13487 {
13488   DelayedEventCallback cb = GetDelayedEvent();
13489   if ((cb == InitBackEnd3 && cps == &first) ||
13490       (cb == TwoMachinesEventIfReady && cps == &second)) {
13491     CancelDelayedEvent();
13492     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13493   }
13494   cps->initDone = val;
13495 }
13496
13497 /* Parse feature command from engine */
13498 void
13499 ParseFeatures(args, cps)
13500      char* args;
13501      ChessProgramState *cps;  
13502 {
13503   char *p = args;
13504   char *q;
13505   int val;
13506   char buf[MSG_SIZ];
13507
13508   for (;;) {
13509     while (*p == ' ') p++;
13510     if (*p == NULLCHAR) return;
13511
13512     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13513     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13514     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13515     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13516     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13517     if (BoolFeature(&p, "reuse", &val, cps)) {
13518       /* Engine can disable reuse, but can't enable it if user said no */
13519       if (!val) cps->reuse = FALSE;
13520       continue;
13521     }
13522     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13523     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13524       if (gameMode == TwoMachinesPlay) {
13525         DisplayTwoMachinesTitle();
13526       } else {
13527         DisplayTitle("");
13528       }
13529       continue;
13530     }
13531     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13532     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13533     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13534     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13535     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13536     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13537     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13538     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13539     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13540     if (IntFeature(&p, "done", &val, cps)) {
13541       FeatureDone(cps, val);
13542       continue;
13543     }
13544     /* Added by Tord: */
13545     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13546     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13547     /* End of additions by Tord */
13548
13549     /* [HGM] added features: */
13550     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13551     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13552     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13553     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13554     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13555     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13556     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13557         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13558             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13559             SendToProgram(buf, cps);
13560             continue;
13561         }
13562         if(cps->nrOptions >= MAX_OPTIONS) {
13563             cps->nrOptions--;
13564             sprintf(buf, "%s engine has too many options\n", cps->which);
13565             DisplayError(buf, 0);
13566         }
13567         continue;
13568     }
13569     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13570     /* End of additions by HGM */
13571
13572     /* unknown feature: complain and skip */
13573     q = p;
13574     while (*q && *q != '=') q++;
13575     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13576     SendToProgram(buf, cps);
13577     p = q;
13578     if (*p == '=') {
13579       p++;
13580       if (*p == '\"') {
13581         p++;
13582         while (*p && *p != '\"') p++;
13583         if (*p == '\"') p++;
13584       } else {
13585         while (*p && *p != ' ') p++;
13586       }
13587     }
13588   }
13589
13590 }
13591
13592 void
13593 PeriodicUpdatesEvent(newState)
13594      int newState;
13595 {
13596     if (newState == appData.periodicUpdates)
13597       return;
13598
13599     appData.periodicUpdates=newState;
13600
13601     /* Display type changes, so update it now */
13602 //    DisplayAnalysis();
13603
13604     /* Get the ball rolling again... */
13605     if (newState) {
13606         AnalysisPeriodicEvent(1);
13607         StartAnalysisClock();
13608     }
13609 }
13610
13611 void
13612 PonderNextMoveEvent(newState)
13613      int newState;
13614 {
13615     if (newState == appData.ponderNextMove) return;
13616     if (gameMode == EditPosition) EditPositionDone(TRUE);
13617     if (newState) {
13618         SendToProgram("hard\n", &first);
13619         if (gameMode == TwoMachinesPlay) {
13620             SendToProgram("hard\n", &second);
13621         }
13622     } else {
13623         SendToProgram("easy\n", &first);
13624         thinkOutput[0] = NULLCHAR;
13625         if (gameMode == TwoMachinesPlay) {
13626             SendToProgram("easy\n", &second);
13627         }
13628     }
13629     appData.ponderNextMove = newState;
13630 }
13631
13632 void
13633 NewSettingEvent(option, command, value)
13634      char *command;
13635      int option, value;
13636 {
13637     char buf[MSG_SIZ];
13638
13639     if (gameMode == EditPosition) EditPositionDone(TRUE);
13640     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13641     SendToProgram(buf, &first);
13642     if (gameMode == TwoMachinesPlay) {
13643         SendToProgram(buf, &second);
13644     }
13645 }
13646
13647 void
13648 ShowThinkingEvent()
13649 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13650 {
13651     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13652     int newState = appData.showThinking
13653         // [HGM] thinking: other features now need thinking output as well
13654         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13655     
13656     if (oldState == newState) return;
13657     oldState = newState;
13658     if (gameMode == EditPosition) EditPositionDone(TRUE);
13659     if (oldState) {
13660         SendToProgram("post\n", &first);
13661         if (gameMode == TwoMachinesPlay) {
13662             SendToProgram("post\n", &second);
13663         }
13664     } else {
13665         SendToProgram("nopost\n", &first);
13666         thinkOutput[0] = NULLCHAR;
13667         if (gameMode == TwoMachinesPlay) {
13668             SendToProgram("nopost\n", &second);
13669         }
13670     }
13671 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13672 }
13673
13674 void
13675 AskQuestionEvent(title, question, replyPrefix, which)
13676      char *title; char *question; char *replyPrefix; char *which;
13677 {
13678   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13679   if (pr == NoProc) return;
13680   AskQuestion(title, question, replyPrefix, pr);
13681 }
13682
13683 void
13684 DisplayMove(moveNumber)
13685      int moveNumber;
13686 {
13687     char message[MSG_SIZ];
13688     char res[MSG_SIZ];
13689     char cpThinkOutput[MSG_SIZ];
13690
13691     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13692     
13693     if (moveNumber == forwardMostMove - 1 || 
13694         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13695
13696         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13697
13698         if (strchr(cpThinkOutput, '\n')) {
13699             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13700         }
13701     } else {
13702         *cpThinkOutput = NULLCHAR;
13703     }
13704
13705     /* [AS] Hide thinking from human user */
13706     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13707         *cpThinkOutput = NULLCHAR;
13708         if( thinkOutput[0] != NULLCHAR ) {
13709             int i;
13710
13711             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13712                 cpThinkOutput[i] = '.';
13713             }
13714             cpThinkOutput[i] = NULLCHAR;
13715             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13716         }
13717     }
13718
13719     if (moveNumber == forwardMostMove - 1 &&
13720         gameInfo.resultDetails != NULL) {
13721         if (gameInfo.resultDetails[0] == NULLCHAR) {
13722             sprintf(res, " %s", PGNResult(gameInfo.result));
13723         } else {
13724             sprintf(res, " {%s} %s",
13725                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13726         }
13727     } else {
13728         res[0] = NULLCHAR;
13729     }
13730
13731     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13732         DisplayMessage(res, cpThinkOutput);
13733     } else {
13734         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13735                 WhiteOnMove(moveNumber) ? " " : ".. ",
13736                 parseList[moveNumber], res);
13737         DisplayMessage(message, cpThinkOutput);
13738     }
13739 }
13740
13741 void
13742 DisplayComment(moveNumber, text)
13743      int moveNumber;
13744      char *text;
13745 {
13746     char title[MSG_SIZ];
13747     char buf[8000]; // comment can be long!
13748     int score, depth;
13749     
13750     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13751       strcpy(title, "Comment");
13752     } else {
13753       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13754               WhiteOnMove(moveNumber) ? " " : ".. ",
13755               parseList[moveNumber]);
13756     }
13757     // [HGM] PV info: display PV info together with (or as) comment
13758     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13759       if(text == NULL) text = "";                                           
13760       score = pvInfoList[moveNumber].score;
13761       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13762               depth, (pvInfoList[moveNumber].time+50)/100, text);
13763       text = buf;
13764     }
13765     if (text != NULL && (appData.autoDisplayComment || commentUp))
13766         CommentPopUp(title, text);
13767 }
13768
13769 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13770  * might be busy thinking or pondering.  It can be omitted if your
13771  * gnuchess is configured to stop thinking immediately on any user
13772  * input.  However, that gnuchess feature depends on the FIONREAD
13773  * ioctl, which does not work properly on some flavors of Unix.
13774  */
13775 void
13776 Attention(cps)
13777      ChessProgramState *cps;
13778 {
13779 #if ATTENTION
13780     if (!cps->useSigint) return;
13781     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13782     switch (gameMode) {
13783       case MachinePlaysWhite:
13784       case MachinePlaysBlack:
13785       case TwoMachinesPlay:
13786       case IcsPlayingWhite:
13787       case IcsPlayingBlack:
13788       case AnalyzeMode:
13789       case AnalyzeFile:
13790         /* Skip if we know it isn't thinking */
13791         if (!cps->maybeThinking) return;
13792         if (appData.debugMode)
13793           fprintf(debugFP, "Interrupting %s\n", cps->which);
13794         InterruptChildProcess(cps->pr);
13795         cps->maybeThinking = FALSE;
13796         break;
13797       default:
13798         break;
13799     }
13800 #endif /*ATTENTION*/
13801 }
13802
13803 int
13804 CheckFlags()
13805 {
13806     if (whiteTimeRemaining <= 0) {
13807         if (!whiteFlag) {
13808             whiteFlag = TRUE;
13809             if (appData.icsActive) {
13810                 if (appData.autoCallFlag &&
13811                     gameMode == IcsPlayingBlack && !blackFlag) {
13812                   SendToICS(ics_prefix);
13813                   SendToICS("flag\n");
13814                 }
13815             } else {
13816                 if (blackFlag) {
13817                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13818                 } else {
13819                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13820                     if (appData.autoCallFlag) {
13821                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13822                         return TRUE;
13823                     }
13824                 }
13825             }
13826         }
13827     }
13828     if (blackTimeRemaining <= 0) {
13829         if (!blackFlag) {
13830             blackFlag = TRUE;
13831             if (appData.icsActive) {
13832                 if (appData.autoCallFlag &&
13833                     gameMode == IcsPlayingWhite && !whiteFlag) {
13834                   SendToICS(ics_prefix);
13835                   SendToICS("flag\n");
13836                 }
13837             } else {
13838                 if (whiteFlag) {
13839                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13840                 } else {
13841                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13842                     if (appData.autoCallFlag) {
13843                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13844                         return TRUE;
13845                     }
13846                 }
13847             }
13848         }
13849     }
13850     return FALSE;
13851 }
13852
13853 void
13854 CheckTimeControl()
13855 {
13856     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13857         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13858
13859     /*
13860      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13861      */
13862     if ( !WhiteOnMove(forwardMostMove) )
13863         /* White made time control */
13864         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13865         /* [HGM] time odds: correct new time quota for time odds! */
13866                                             / WhitePlayer()->timeOdds;
13867       else
13868         /* Black made time control */
13869         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13870                                             / WhitePlayer()->other->timeOdds;
13871 }
13872
13873 void
13874 DisplayBothClocks()
13875 {
13876     int wom = gameMode == EditPosition ?
13877       !blackPlaysFirst : WhiteOnMove(currentMove);
13878     DisplayWhiteClock(whiteTimeRemaining, wom);
13879     DisplayBlackClock(blackTimeRemaining, !wom);
13880 }
13881
13882
13883 /* Timekeeping seems to be a portability nightmare.  I think everyone
13884    has ftime(), but I'm really not sure, so I'm including some ifdefs
13885    to use other calls if you don't.  Clocks will be less accurate if
13886    you have neither ftime nor gettimeofday.
13887 */
13888
13889 /* VS 2008 requires the #include outside of the function */
13890 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13891 #include <sys/timeb.h>
13892 #endif
13893
13894 /* Get the current time as a TimeMark */
13895 void
13896 GetTimeMark(tm)
13897      TimeMark *tm;
13898 {
13899 #if HAVE_GETTIMEOFDAY
13900
13901     struct timeval timeVal;
13902     struct timezone timeZone;
13903
13904     gettimeofday(&timeVal, &timeZone);
13905     tm->sec = (long) timeVal.tv_sec; 
13906     tm->ms = (int) (timeVal.tv_usec / 1000L);
13907
13908 #else /*!HAVE_GETTIMEOFDAY*/
13909 #if HAVE_FTIME
13910
13911 // include <sys/timeb.h> / moved to just above start of function
13912     struct timeb timeB;
13913
13914     ftime(&timeB);
13915     tm->sec = (long) timeB.time;
13916     tm->ms = (int) timeB.millitm;
13917
13918 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13919     tm->sec = (long) time(NULL);
13920     tm->ms = 0;
13921 #endif
13922 #endif
13923 }
13924
13925 /* Return the difference in milliseconds between two
13926    time marks.  We assume the difference will fit in a long!
13927 */
13928 long
13929 SubtractTimeMarks(tm2, tm1)
13930      TimeMark *tm2, *tm1;
13931 {
13932     return 1000L*(tm2->sec - tm1->sec) +
13933            (long) (tm2->ms - tm1->ms);
13934 }
13935
13936
13937 /*
13938  * Code to manage the game clocks.
13939  *
13940  * In tournament play, black starts the clock and then white makes a move.
13941  * We give the human user a slight advantage if he is playing white---the
13942  * clocks don't run until he makes his first move, so it takes zero time.
13943  * Also, we don't account for network lag, so we could get out of sync
13944  * with GNU Chess's clock -- but then, referees are always right.  
13945  */
13946
13947 static TimeMark tickStartTM;
13948 static long intendedTickLength;
13949
13950 long
13951 NextTickLength(timeRemaining)
13952      long timeRemaining;
13953 {
13954     long nominalTickLength, nextTickLength;
13955
13956     if (timeRemaining > 0L && timeRemaining <= 10000L)
13957       nominalTickLength = 100L;
13958     else
13959       nominalTickLength = 1000L;
13960     nextTickLength = timeRemaining % nominalTickLength;
13961     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13962
13963     return nextTickLength;
13964 }
13965
13966 /* Adjust clock one minute up or down */
13967 void
13968 AdjustClock(Boolean which, int dir)
13969 {
13970     if(which) blackTimeRemaining += 60000*dir;
13971     else      whiteTimeRemaining += 60000*dir;
13972     DisplayBothClocks();
13973 }
13974
13975 /* Stop clocks and reset to a fresh time control */
13976 void
13977 ResetClocks() 
13978 {
13979     (void) StopClockTimer();
13980     if (appData.icsActive) {
13981         whiteTimeRemaining = blackTimeRemaining = 0;
13982     } else if (searchTime) {
13983         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13984         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13985     } else { /* [HGM] correct new time quote for time odds */
13986         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13987         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13988     }
13989     if (whiteFlag || blackFlag) {
13990         DisplayTitle("");
13991         whiteFlag = blackFlag = FALSE;
13992     }
13993     DisplayBothClocks();
13994 }
13995
13996 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13997
13998 /* Decrement running clock by amount of time that has passed */
13999 void
14000 DecrementClocks()
14001 {
14002     long timeRemaining;
14003     long lastTickLength, fudge;
14004     TimeMark now;
14005
14006     if (!appData.clockMode) return;
14007     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14008         
14009     GetTimeMark(&now);
14010
14011     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14012
14013     /* Fudge if we woke up a little too soon */
14014     fudge = intendedTickLength - lastTickLength;
14015     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14016
14017     if (WhiteOnMove(forwardMostMove)) {
14018         if(whiteNPS >= 0) lastTickLength = 0;
14019         timeRemaining = whiteTimeRemaining -= lastTickLength;
14020         DisplayWhiteClock(whiteTimeRemaining - fudge,
14021                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14022     } else {
14023         if(blackNPS >= 0) lastTickLength = 0;
14024         timeRemaining = blackTimeRemaining -= lastTickLength;
14025         DisplayBlackClock(blackTimeRemaining - fudge,
14026                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14027     }
14028
14029     if (CheckFlags()) return;
14030         
14031     tickStartTM = now;
14032     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14033     StartClockTimer(intendedTickLength);
14034
14035     /* if the time remaining has fallen below the alarm threshold, sound the
14036      * alarm. if the alarm has sounded and (due to a takeback or time control
14037      * with increment) the time remaining has increased to a level above the
14038      * threshold, reset the alarm so it can sound again. 
14039      */
14040     
14041     if (appData.icsActive && appData.icsAlarm) {
14042
14043         /* make sure we are dealing with the user's clock */
14044         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14045                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14046            )) return;
14047
14048         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14049             alarmSounded = FALSE;
14050         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14051             PlayAlarmSound();
14052             alarmSounded = TRUE;
14053         }
14054     }
14055 }
14056
14057
14058 /* A player has just moved, so stop the previously running
14059    clock and (if in clock mode) start the other one.
14060    We redisplay both clocks in case we're in ICS mode, because
14061    ICS gives us an update to both clocks after every move.
14062    Note that this routine is called *after* forwardMostMove
14063    is updated, so the last fractional tick must be subtracted
14064    from the color that is *not* on move now.
14065 */
14066 void
14067 SwitchClocks(int newMoveNr)
14068 {
14069     long lastTickLength;
14070     TimeMark now;
14071     int flagged = FALSE;
14072
14073     GetTimeMark(&now);
14074
14075     if (StopClockTimer() && appData.clockMode) {
14076         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14077         if (!WhiteOnMove(forwardMostMove)) {
14078             if(blackNPS >= 0) lastTickLength = 0;
14079             blackTimeRemaining -= lastTickLength;
14080            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14081 //         if(pvInfoList[forwardMostMove-1].time == -1)
14082                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14083                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14084         } else {
14085            if(whiteNPS >= 0) lastTickLength = 0;
14086            whiteTimeRemaining -= lastTickLength;
14087            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14088 //         if(pvInfoList[forwardMostMove-1].time == -1)
14089                  pvInfoList[forwardMostMove-1].time = 
14090                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14091         }
14092         flagged = CheckFlags();
14093     }
14094     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14095     CheckTimeControl();
14096
14097     if (flagged || !appData.clockMode) return;
14098
14099     switch (gameMode) {
14100       case MachinePlaysBlack:
14101       case MachinePlaysWhite:
14102       case BeginningOfGame:
14103         if (pausing) return;
14104         break;
14105
14106       case EditGame:
14107       case PlayFromGameFile:
14108       case IcsExamining:
14109         return;
14110
14111       default:
14112         break;
14113     }
14114
14115     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14116         if(WhiteOnMove(forwardMostMove))
14117              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14118         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14119     }
14120
14121     tickStartTM = now;
14122     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14123       whiteTimeRemaining : blackTimeRemaining);
14124     StartClockTimer(intendedTickLength);
14125 }
14126         
14127
14128 /* Stop both clocks */
14129 void
14130 StopClocks()
14131 {       
14132     long lastTickLength;
14133     TimeMark now;
14134
14135     if (!StopClockTimer()) return;
14136     if (!appData.clockMode) return;
14137
14138     GetTimeMark(&now);
14139
14140     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14141     if (WhiteOnMove(forwardMostMove)) {
14142         if(whiteNPS >= 0) lastTickLength = 0;
14143         whiteTimeRemaining -= lastTickLength;
14144         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14145     } else {
14146         if(blackNPS >= 0) lastTickLength = 0;
14147         blackTimeRemaining -= lastTickLength;
14148         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14149     }
14150     CheckFlags();
14151 }
14152         
14153 /* Start clock of player on move.  Time may have been reset, so
14154    if clock is already running, stop and restart it. */
14155 void
14156 StartClocks()
14157 {
14158     (void) StopClockTimer(); /* in case it was running already */
14159     DisplayBothClocks();
14160     if (CheckFlags()) return;
14161
14162     if (!appData.clockMode) return;
14163     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14164
14165     GetTimeMark(&tickStartTM);
14166     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14167       whiteTimeRemaining : blackTimeRemaining);
14168
14169    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14170     whiteNPS = blackNPS = -1; 
14171     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14172        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14173         whiteNPS = first.nps;
14174     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14175        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14176         blackNPS = first.nps;
14177     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14178         whiteNPS = second.nps;
14179     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14180         blackNPS = second.nps;
14181     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14182
14183     StartClockTimer(intendedTickLength);
14184 }
14185
14186 char *
14187 TimeString(ms)
14188      long ms;
14189 {
14190     long second, minute, hour, day;
14191     char *sign = "";
14192     static char buf[32];
14193     
14194     if (ms > 0 && ms <= 9900) {
14195       /* convert milliseconds to tenths, rounding up */
14196       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14197
14198       sprintf(buf, " %03.1f ", tenths/10.0);
14199       return buf;
14200     }
14201
14202     /* convert milliseconds to seconds, rounding up */
14203     /* use floating point to avoid strangeness of integer division
14204        with negative dividends on many machines */
14205     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14206
14207     if (second < 0) {
14208         sign = "-";
14209         second = -second;
14210     }
14211     
14212     day = second / (60 * 60 * 24);
14213     second = second % (60 * 60 * 24);
14214     hour = second / (60 * 60);
14215     second = second % (60 * 60);
14216     minute = second / 60;
14217     second = second % 60;
14218     
14219     if (day > 0)
14220       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14221               sign, day, hour, minute, second);
14222     else if (hour > 0)
14223       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14224     else
14225       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14226     
14227     return buf;
14228 }
14229
14230
14231 /*
14232  * This is necessary because some C libraries aren't ANSI C compliant yet.
14233  */
14234 char *
14235 StrStr(string, match)
14236      char *string, *match;
14237 {
14238     int i, length;
14239     
14240     length = strlen(match);
14241     
14242     for (i = strlen(string) - length; i >= 0; i--, string++)
14243       if (!strncmp(match, string, length))
14244         return string;
14245     
14246     return NULL;
14247 }
14248
14249 char *
14250 StrCaseStr(string, match)
14251      char *string, *match;
14252 {
14253     int i, j, length;
14254     
14255     length = strlen(match);
14256     
14257     for (i = strlen(string) - length; i >= 0; i--, string++) {
14258         for (j = 0; j < length; j++) {
14259             if (ToLower(match[j]) != ToLower(string[j]))
14260               break;
14261         }
14262         if (j == length) return string;
14263     }
14264
14265     return NULL;
14266 }
14267
14268 #ifndef _amigados
14269 int
14270 StrCaseCmp(s1, s2)
14271      char *s1, *s2;
14272 {
14273     char c1, c2;
14274     
14275     for (;;) {
14276         c1 = ToLower(*s1++);
14277         c2 = ToLower(*s2++);
14278         if (c1 > c2) return 1;
14279         if (c1 < c2) return -1;
14280         if (c1 == NULLCHAR) return 0;
14281     }
14282 }
14283
14284
14285 int
14286 ToLower(c)
14287      int c;
14288 {
14289     return isupper(c) ? tolower(c) : c;
14290 }
14291
14292
14293 int
14294 ToUpper(c)
14295      int c;
14296 {
14297     return islower(c) ? toupper(c) : c;
14298 }
14299 #endif /* !_amigados    */
14300
14301 char *
14302 StrSave(s)
14303      char *s;
14304 {
14305     char *ret;
14306
14307     if ((ret = (char *) malloc(strlen(s) + 1))) {
14308         strcpy(ret, s);
14309     }
14310     return ret;
14311 }
14312
14313 char *
14314 StrSavePtr(s, savePtr)
14315      char *s, **savePtr;
14316 {
14317     if (*savePtr) {
14318         free(*savePtr);
14319     }
14320     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14321         strcpy(*savePtr, s);
14322     }
14323     return(*savePtr);
14324 }
14325
14326 char *
14327 PGNDate()
14328 {
14329     time_t clock;
14330     struct tm *tm;
14331     char buf[MSG_SIZ];
14332
14333     clock = time((time_t *)NULL);
14334     tm = localtime(&clock);
14335     sprintf(buf, "%04d.%02d.%02d",
14336             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14337     return StrSave(buf);
14338 }
14339
14340
14341 char *
14342 PositionToFEN(move, overrideCastling)
14343      int move;
14344      char *overrideCastling;
14345 {
14346     int i, j, fromX, fromY, toX, toY;
14347     int whiteToPlay;
14348     char buf[128];
14349     char *p, *q;
14350     int emptycount;
14351     ChessSquare piece;
14352
14353     whiteToPlay = (gameMode == EditPosition) ?
14354       !blackPlaysFirst : (move % 2 == 0);
14355     p = buf;
14356
14357     /* Piece placement data */
14358     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14359         emptycount = 0;
14360         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14361             if (boards[move][i][j] == EmptySquare) {
14362                 emptycount++;
14363             } else { ChessSquare piece = boards[move][i][j];
14364                 if (emptycount > 0) {
14365                     if(emptycount<10) /* [HGM] can be >= 10 */
14366                         *p++ = '0' + emptycount;
14367                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14368                     emptycount = 0;
14369                 }
14370                 if(PieceToChar(piece) == '+') {
14371                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14372                     *p++ = '+';
14373                     piece = (ChessSquare)(DEMOTED piece);
14374                 } 
14375                 *p++ = PieceToChar(piece);
14376                 if(p[-1] == '~') {
14377                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14378                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14379                     *p++ = '~';
14380                 }
14381             }
14382         }
14383         if (emptycount > 0) {
14384             if(emptycount<10) /* [HGM] can be >= 10 */
14385                 *p++ = '0' + emptycount;
14386             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14387             emptycount = 0;
14388         }
14389         *p++ = '/';
14390     }
14391     *(p - 1) = ' ';
14392
14393     /* [HGM] print Crazyhouse or Shogi holdings */
14394     if( gameInfo.holdingsWidth ) {
14395         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14396         q = p;
14397         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14398             piece = boards[move][i][BOARD_WIDTH-1];
14399             if( piece != EmptySquare )
14400               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14401                   *p++ = PieceToChar(piece);
14402         }
14403         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14404             piece = boards[move][BOARD_HEIGHT-i-1][0];
14405             if( piece != EmptySquare )
14406               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14407                   *p++ = PieceToChar(piece);
14408         }
14409
14410         if( q == p ) *p++ = '-';
14411         *p++ = ']';
14412         *p++ = ' ';
14413     }
14414
14415     /* Active color */
14416     *p++ = whiteToPlay ? 'w' : 'b';
14417     *p++ = ' ';
14418
14419   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14420     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14421   } else {
14422   if(nrCastlingRights) {
14423      q = p;
14424      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14425        /* [HGM] write directly from rights */
14426            if(boards[move][CASTLING][2] != NoRights &&
14427               boards[move][CASTLING][0] != NoRights   )
14428                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14429            if(boards[move][CASTLING][2] != NoRights &&
14430               boards[move][CASTLING][1] != NoRights   )
14431                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14432            if(boards[move][CASTLING][5] != NoRights &&
14433               boards[move][CASTLING][3] != NoRights   )
14434                 *p++ = boards[move][CASTLING][3] + AAA;
14435            if(boards[move][CASTLING][5] != NoRights &&
14436               boards[move][CASTLING][4] != NoRights   )
14437                 *p++ = boards[move][CASTLING][4] + AAA;
14438      } else {
14439
14440         /* [HGM] write true castling rights */
14441         if( nrCastlingRights == 6 ) {
14442             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14443                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14444             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14445                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14446             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14447                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14448             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14449                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14450         }
14451      }
14452      if (q == p) *p++ = '-'; /* No castling rights */
14453      *p++ = ' ';
14454   }
14455
14456   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14457      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14458     /* En passant target square */
14459     if (move > backwardMostMove) {
14460         fromX = moveList[move - 1][0] - AAA;
14461         fromY = moveList[move - 1][1] - ONE;
14462         toX = moveList[move - 1][2] - AAA;
14463         toY = moveList[move - 1][3] - ONE;
14464         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14465             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14466             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14467             fromX == toX) {
14468             /* 2-square pawn move just happened */
14469             *p++ = toX + AAA;
14470             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14471         } else {
14472             *p++ = '-';
14473         }
14474     } else if(move == backwardMostMove) {
14475         // [HGM] perhaps we should always do it like this, and forget the above?
14476         if((signed char)boards[move][EP_STATUS] >= 0) {
14477             *p++ = boards[move][EP_STATUS] + AAA;
14478             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14479         } else {
14480             *p++ = '-';
14481         }
14482     } else {
14483         *p++ = '-';
14484     }
14485     *p++ = ' ';
14486   }
14487   }
14488
14489     /* [HGM] find reversible plies */
14490     {   int i = 0, j=move;
14491
14492         if (appData.debugMode) { int k;
14493             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14494             for(k=backwardMostMove; k<=forwardMostMove; k++)
14495                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14496
14497         }
14498
14499         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14500         if( j == backwardMostMove ) i += initialRulePlies;
14501         sprintf(p, "%d ", i);
14502         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14503     }
14504     /* Fullmove number */
14505     sprintf(p, "%d", (move / 2) + 1);
14506     
14507     return StrSave(buf);
14508 }
14509
14510 Boolean
14511 ParseFEN(board, blackPlaysFirst, fen)
14512     Board board;
14513      int *blackPlaysFirst;
14514      char *fen;
14515 {
14516     int i, j;
14517     char *p;
14518     int emptycount;
14519     ChessSquare piece;
14520
14521     p = fen;
14522
14523     /* [HGM] by default clear Crazyhouse holdings, if present */
14524     if(gameInfo.holdingsWidth) {
14525        for(i=0; i<BOARD_HEIGHT; i++) {
14526            board[i][0]             = EmptySquare; /* black holdings */
14527            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14528            board[i][1]             = (ChessSquare) 0; /* black counts */
14529            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14530        }
14531     }
14532
14533     /* Piece placement data */
14534     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14535         j = 0;
14536         for (;;) {
14537             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14538                 if (*p == '/') p++;
14539                 emptycount = gameInfo.boardWidth - j;
14540                 while (emptycount--)
14541                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14542                 break;
14543 #if(BOARD_FILES >= 10)
14544             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14545                 p++; emptycount=10;
14546                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14547                 while (emptycount--)
14548                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14549 #endif
14550             } else if (isdigit(*p)) {
14551                 emptycount = *p++ - '0';
14552                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14553                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14554                 while (emptycount--)
14555                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14556             } else if (*p == '+' || isalpha(*p)) {
14557                 if (j >= gameInfo.boardWidth) return FALSE;
14558                 if(*p=='+') {
14559                     piece = CharToPiece(*++p);
14560                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14561                     piece = (ChessSquare) (PROMOTED piece ); p++;
14562                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14563                 } else piece = CharToPiece(*p++);
14564
14565                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14566                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14567                     piece = (ChessSquare) (PROMOTED piece);
14568                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14569                     p++;
14570                 }
14571                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14572             } else {
14573                 return FALSE;
14574             }
14575         }
14576     }
14577     while (*p == '/' || *p == ' ') p++;
14578
14579     /* [HGM] look for Crazyhouse holdings here */
14580     while(*p==' ') p++;
14581     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14582         if(*p == '[') p++;
14583         if(*p == '-' ) *p++; /* empty holdings */ else {
14584             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14585             /* if we would allow FEN reading to set board size, we would   */
14586             /* have to add holdings and shift the board read so far here   */
14587             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14588                 *p++;
14589                 if((int) piece >= (int) BlackPawn ) {
14590                     i = (int)piece - (int)BlackPawn;
14591                     i = PieceToNumber((ChessSquare)i);
14592                     if( i >= gameInfo.holdingsSize ) return FALSE;
14593                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14594                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14595                 } else {
14596                     i = (int)piece - (int)WhitePawn;
14597                     i = PieceToNumber((ChessSquare)i);
14598                     if( i >= gameInfo.holdingsSize ) return FALSE;
14599                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14600                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14601                 }
14602             }
14603         }
14604         if(*p == ']') *p++;
14605     }
14606
14607     while(*p == ' ') p++;
14608
14609     /* Active color */
14610     switch (*p++) {
14611       case 'w':
14612         *blackPlaysFirst = FALSE;
14613         break;
14614       case 'b': 
14615         *blackPlaysFirst = TRUE;
14616         break;
14617       default:
14618         return FALSE;
14619     }
14620
14621     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14622     /* return the extra info in global variiables             */
14623
14624     /* set defaults in case FEN is incomplete */
14625     board[EP_STATUS] = EP_UNKNOWN;
14626     for(i=0; i<nrCastlingRights; i++ ) {
14627         board[CASTLING][i] =
14628             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14629     }   /* assume possible unless obviously impossible */
14630     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14631     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14632     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14633                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14634     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14635     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14636     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14637                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14638     FENrulePlies = 0;
14639
14640     while(*p==' ') p++;
14641     if(nrCastlingRights) {
14642       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14643           /* castling indicator present, so default becomes no castlings */
14644           for(i=0; i<nrCastlingRights; i++ ) {
14645                  board[CASTLING][i] = NoRights;
14646           }
14647       }
14648       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14649              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14650              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14651              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14652         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14653
14654         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14655             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14656             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14657         }
14658         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14659             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14660         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14661                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14662         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14663                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14664         switch(c) {
14665           case'K':
14666               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14667               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14668               board[CASTLING][2] = whiteKingFile;
14669               break;
14670           case'Q':
14671               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14672               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14673               board[CASTLING][2] = whiteKingFile;
14674               break;
14675           case'k':
14676               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14677               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14678               board[CASTLING][5] = blackKingFile;
14679               break;
14680           case'q':
14681               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14682               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14683               board[CASTLING][5] = blackKingFile;
14684           case '-':
14685               break;
14686           default: /* FRC castlings */
14687               if(c >= 'a') { /* black rights */
14688                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14689                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14690                   if(i == BOARD_RGHT) break;
14691                   board[CASTLING][5] = i;
14692                   c -= AAA;
14693                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14694                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14695                   if(c > i)
14696                       board[CASTLING][3] = c;
14697                   else
14698                       board[CASTLING][4] = c;
14699               } else { /* white rights */
14700                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14701                     if(board[0][i] == WhiteKing) break;
14702                   if(i == BOARD_RGHT) break;
14703                   board[CASTLING][2] = i;
14704                   c -= AAA - 'a' + 'A';
14705                   if(board[0][c] >= WhiteKing) break;
14706                   if(c > i)
14707                       board[CASTLING][0] = c;
14708                   else
14709                       board[CASTLING][1] = c;
14710               }
14711         }
14712       }
14713       for(i=0; i<nrCastlingRights; i++)
14714         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14715     if (appData.debugMode) {
14716         fprintf(debugFP, "FEN castling rights:");
14717         for(i=0; i<nrCastlingRights; i++)
14718         fprintf(debugFP, " %d", board[CASTLING][i]);
14719         fprintf(debugFP, "\n");
14720     }
14721
14722       while(*p==' ') p++;
14723     }
14724
14725     /* read e.p. field in games that know e.p. capture */
14726     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14727        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14728       if(*p=='-') {
14729         p++; board[EP_STATUS] = EP_NONE;
14730       } else {
14731          char c = *p++ - AAA;
14732
14733          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14734          if(*p >= '0' && *p <='9') *p++;
14735          board[EP_STATUS] = c;
14736       }
14737     }
14738
14739
14740     if(sscanf(p, "%d", &i) == 1) {
14741         FENrulePlies = i; /* 50-move ply counter */
14742         /* (The move number is still ignored)    */
14743     }
14744
14745     return TRUE;
14746 }
14747       
14748 void
14749 EditPositionPasteFEN(char *fen)
14750 {
14751   if (fen != NULL) {
14752     Board initial_position;
14753
14754     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14755       DisplayError(_("Bad FEN position in clipboard"), 0);
14756       return ;
14757     } else {
14758       int savedBlackPlaysFirst = blackPlaysFirst;
14759       EditPositionEvent();
14760       blackPlaysFirst = savedBlackPlaysFirst;
14761       CopyBoard(boards[0], initial_position);
14762       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14763       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14764       DisplayBothClocks();
14765       DrawPosition(FALSE, boards[currentMove]);
14766     }
14767   }
14768 }
14769
14770 static char cseq[12] = "\\   ";
14771
14772 Boolean set_cont_sequence(char *new_seq)
14773 {
14774     int len;
14775     Boolean ret;
14776
14777     // handle bad attempts to set the sequence
14778         if (!new_seq)
14779                 return 0; // acceptable error - no debug
14780
14781     len = strlen(new_seq);
14782     ret = (len > 0) && (len < sizeof(cseq));
14783     if (ret)
14784         strcpy(cseq, new_seq);
14785     else if (appData.debugMode)
14786         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14787     return ret;
14788 }
14789
14790 /*
14791     reformat a source message so words don't cross the width boundary.  internal
14792     newlines are not removed.  returns the wrapped size (no null character unless
14793     included in source message).  If dest is NULL, only calculate the size required
14794     for the dest buffer.  lp argument indicats line position upon entry, and it's
14795     passed back upon exit.
14796 */
14797 int wrap(char *dest, char *src, int count, int width, int *lp)
14798 {
14799     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14800
14801     cseq_len = strlen(cseq);
14802     old_line = line = *lp;
14803     ansi = len = clen = 0;
14804
14805     for (i=0; i < count; i++)
14806     {
14807         if (src[i] == '\033')
14808             ansi = 1;
14809
14810         // if we hit the width, back up
14811         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14812         {
14813             // store i & len in case the word is too long
14814             old_i = i, old_len = len;
14815
14816             // find the end of the last word
14817             while (i && src[i] != ' ' && src[i] != '\n')
14818             {
14819                 i--;
14820                 len--;
14821             }
14822
14823             // word too long?  restore i & len before splitting it
14824             if ((old_i-i+clen) >= width)
14825             {
14826                 i = old_i;
14827                 len = old_len;
14828             }
14829
14830             // extra space?
14831             if (i && src[i-1] == ' ')
14832                 len--;
14833
14834             if (src[i] != ' ' && src[i] != '\n')
14835             {
14836                 i--;
14837                 if (len)
14838                     len--;
14839             }
14840
14841             // now append the newline and continuation sequence
14842             if (dest)
14843                 dest[len] = '\n';
14844             len++;
14845             if (dest)
14846                 strncpy(dest+len, cseq, cseq_len);
14847             len += cseq_len;
14848             line = cseq_len;
14849             clen = cseq_len;
14850             continue;
14851         }
14852
14853         if (dest)
14854             dest[len] = src[i];
14855         len++;
14856         if (!ansi)
14857             line++;
14858         if (src[i] == '\n')
14859             line = 0;
14860         if (src[i] == 'm')
14861             ansi = 0;
14862     }
14863     if (dest && appData.debugMode)
14864     {
14865         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14866             count, width, line, len, *lp);
14867         show_bytes(debugFP, src, count);
14868         fprintf(debugFP, "\ndest: ");
14869         show_bytes(debugFP, dest, len);
14870         fprintf(debugFP, "\n");
14871     }
14872     *lp = dest ? line : old_line;
14873
14874     return len;
14875 }
14876
14877 // [HGM] vari: routines for shelving variations
14878
14879 void 
14880 PushTail(int firstMove, int lastMove)
14881 {
14882         int i, j, nrMoves = lastMove - firstMove;
14883
14884         if(appData.icsActive) { // only in local mode
14885                 forwardMostMove = currentMove; // mimic old ICS behavior
14886                 return;
14887         }
14888         if(storedGames >= MAX_VARIATIONS-1) return;
14889
14890         // push current tail of game on stack
14891         savedResult[storedGames] = gameInfo.result;
14892         savedDetails[storedGames] = gameInfo.resultDetails;
14893         gameInfo.resultDetails = NULL;
14894         savedFirst[storedGames] = firstMove;
14895         savedLast [storedGames] = lastMove;
14896         savedFramePtr[storedGames] = framePtr;
14897         framePtr -= nrMoves; // reserve space for the boards
14898         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14899             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14900             for(j=0; j<MOVE_LEN; j++)
14901                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14902             for(j=0; j<2*MOVE_LEN; j++)
14903                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14904             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14905             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14906             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14907             pvInfoList[firstMove+i-1].depth = 0;
14908             commentList[framePtr+i] = commentList[firstMove+i];
14909             commentList[firstMove+i] = NULL;
14910         }
14911
14912         storedGames++;
14913         forwardMostMove = currentMove; // truncte game so we can start variation
14914         if(storedGames == 1) GreyRevert(FALSE);
14915 }
14916
14917 Boolean
14918 PopTail(Boolean annotate)
14919 {
14920         int i, j, nrMoves;
14921         char buf[8000], moveBuf[20];
14922
14923         if(appData.icsActive) return FALSE; // only in local mode
14924         if(!storedGames) return FALSE; // sanity
14925
14926         storedGames--;
14927         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14928         nrMoves = savedLast[storedGames] - currentMove;
14929         if(annotate) {
14930                 int cnt = 10;
14931                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14932                 else strcpy(buf, "(");
14933                 for(i=currentMove; i<forwardMostMove; i++) {
14934                         if(WhiteOnMove(i))
14935                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14936                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14937                         strcat(buf, moveBuf);
14938                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14939                 }
14940                 strcat(buf, ")");
14941         }
14942         for(i=1; i<nrMoves; i++) { // copy last variation back
14943             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14944             for(j=0; j<MOVE_LEN; j++)
14945                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14946             for(j=0; j<2*MOVE_LEN; j++)
14947                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14948             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14949             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14950             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14951             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14952             commentList[currentMove+i] = commentList[framePtr+i];
14953             commentList[framePtr+i] = NULL;
14954         }
14955         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14956         framePtr = savedFramePtr[storedGames];
14957         gameInfo.result = savedResult[storedGames];
14958         if(gameInfo.resultDetails != NULL) {
14959             free(gameInfo.resultDetails);
14960       }
14961         gameInfo.resultDetails = savedDetails[storedGames];
14962         forwardMostMove = currentMove + nrMoves;
14963         if(storedGames == 0) GreyRevert(TRUE);
14964         return TRUE;
14965 }
14966
14967 void 
14968 CleanupTail()
14969 {       // remove all shelved variations
14970         int i;
14971         for(i=0; i<storedGames; i++) {
14972             if(savedDetails[i])
14973                 free(savedDetails[i]);
14974             savedDetails[i] = NULL;
14975         }
14976         for(i=framePtr; i<MAX_MOVES; i++) {
14977                 if(commentList[i]) free(commentList[i]);
14978                 commentList[i] = NULL;
14979         }
14980         framePtr = MAX_MOVES-1;
14981         storedGames = 0;
14982 }