Seek-Graph bugfix: disappearing output in ICS console
[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-squareSize/8-7)* log(tc)/log(95.) + 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-squareSize/8-7)* log((double)i)/log(95.) + 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                         next_out = i+1; // [HGM] suppress printing in ICS window
2546                     } else
2547                     if(!suppressKibitz) // [HGM] kibitz
2548                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2549                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2550                         int nrDigit = 0, nrAlph = 0, j;
2551                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2552                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2553                         parse[parse_pos] = NULLCHAR;
2554                         // try to be smart: if it does not look like search info, it should go to
2555                         // ICS interaction window after all, not to engine-output window.
2556                         for(j=0; j<parse_pos; j++) { // count letters and digits
2557                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2558                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2559                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2560                         }
2561                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2562                             int depth=0; float score;
2563                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2564                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2565                                 pvInfoList[forwardMostMove-1].depth = depth;
2566                                 pvInfoList[forwardMostMove-1].score = 100*score;
2567                             }
2568                             OutputKibitz(suppressKibitz, parse);
2569                         } else {
2570                             char tmp[MSG_SIZ];
2571                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2572                             SendToPlayer(tmp, strlen(tmp));
2573                         }
2574                         next_out = i+1; // [HGM] suppress printing in ICS window
2575                     }
2576                     started = STARTED_NONE;
2577                 } else {
2578                     /* Don't match patterns against characters in comment */
2579                     i++;
2580                     continue;
2581                 }
2582             }
2583             if (started == STARTED_CHATTER) {
2584                 if (buf[i] != '\n') {
2585                     /* Don't match patterns against characters in chatter */
2586                     i++;
2587                     continue;
2588                 }
2589                 started = STARTED_NONE;
2590                 if(suppressKibitz) next_out = i+1;
2591             }
2592
2593             /* Kludge to deal with rcmd protocol */
2594             if (firstTime && looking_at(buf, &i, "\001*")) {
2595                 DisplayFatalError(&buf[1], 0, 1);
2596                 continue;
2597             } else {
2598                 firstTime = FALSE;
2599             }
2600
2601             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2602                 ics_type = ICS_ICC;
2603                 ics_prefix = "/";
2604                 if (appData.debugMode)
2605                   fprintf(debugFP, "ics_type %d\n", ics_type);
2606                 continue;
2607             }
2608             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2609                 ics_type = ICS_FICS;
2610                 ics_prefix = "$";
2611                 if (appData.debugMode)
2612                   fprintf(debugFP, "ics_type %d\n", ics_type);
2613                 continue;
2614             }
2615             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2616                 ics_type = ICS_CHESSNET;
2617                 ics_prefix = "/";
2618                 if (appData.debugMode)
2619                   fprintf(debugFP, "ics_type %d\n", ics_type);
2620                 continue;
2621             }
2622
2623             if (!loggedOn &&
2624                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2625                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2626                  looking_at(buf, &i, "will be \"*\""))) {
2627               strcpy(ics_handle, star_match[0]);
2628               continue;
2629             }
2630
2631             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2632               char buf[MSG_SIZ];
2633               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2634               DisplayIcsInteractionTitle(buf);
2635               have_set_title = TRUE;
2636             }
2637
2638             /* skip finger notes */
2639             if (started == STARTED_NONE &&
2640                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2641                  (buf[i] == '1' && buf[i+1] == '0')) &&
2642                 buf[i+2] == ':' && buf[i+3] == ' ') {
2643               started = STARTED_CHATTER;
2644               i += 3;
2645               continue;
2646             }
2647
2648             oldi = i;
2649             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2650             if(appData.seekGraph) {
2651                 if(soughtPending && MatchSoughtLine(buf+i)) {
2652                     i = strstr(buf+i, "rated") - buf;
2653                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2654                     next_out = leftover_start = i;
2655                     started = STARTED_CHATTER;
2656                     suppressKibitz = TRUE;
2657                     continue;
2658                 }
2659                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2660                         && looking_at(buf, &i, "* ads displayed")) {
2661                     soughtPending = FALSE;
2662                     seekGraphUp = TRUE;
2663                     DrawSeekGraph();
2664                     continue;
2665                 }
2666                 if(appData.autoRefresh) {
2667                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2668                         int s = (ics_type == ICS_ICC); // ICC format differs
2669                         if(seekGraphUp)
2670                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2671                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2672                         looking_at(buf, &i, "*% "); // eat prompt
2673                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2674                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2675                         next_out = i; // suppress
2676                         continue;
2677                     }
2678                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2679                         char *p = star_match[0];
2680                         while(*p) {
2681                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2682                             while(*p && *p++ != ' '); // next
2683                         }
2684                         looking_at(buf, &i, "*% "); // eat prompt
2685                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2686                         next_out = i;
2687                         continue;
2688                     }
2689                 }
2690             }
2691
2692             /* skip formula vars */
2693             if (started == STARTED_NONE &&
2694                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2695               started = STARTED_CHATTER;
2696               i += 3;
2697               continue;
2698             }
2699
2700             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2701             if (appData.autoKibitz && started == STARTED_NONE && 
2702                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2703                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2704                 if(looking_at(buf, &i, "* kibitzes: ") &&
2705                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2706                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2707                         suppressKibitz = TRUE;
2708                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2709                         next_out = i;
2710                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2711                                 && (gameMode == IcsPlayingWhite)) ||
2712                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2713                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2714                             started = STARTED_CHATTER; // own kibitz we simply discard
2715                         else {
2716                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2717                             parse_pos = 0; parse[0] = NULLCHAR;
2718                             savingComment = TRUE;
2719                             suppressKibitz = gameMode != IcsObserving ? 2 :
2720                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2721                         } 
2722                         continue;
2723                 } else
2724                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2725                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2726                          && atoi(star_match[0])) {
2727                     // suppress the acknowledgements of our own autoKibitz
2728                     char *p;
2729                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2730                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2731                     SendToPlayer(star_match[0], strlen(star_match[0]));
2732                     if(looking_at(buf, &i, "*% ")) // eat prompt
2733                         suppressKibitz = FALSE;
2734                     next_out = i;
2735                     continue;
2736                 }
2737             } // [HGM] kibitz: end of patch
2738
2739             // [HGM] chat: intercept tells by users for which we have an open chat window
2740             channel = -1;
2741             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2742                                            looking_at(buf, &i, "* whispers:") ||
2743                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2744                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2745                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2746                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2747                 int p;
2748                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2749                 chattingPartner = -1;
2750
2751                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2752                 for(p=0; p<MAX_CHAT; p++) {
2753                     if(channel == atoi(chatPartner[p])) {
2754                     talker[0] = '['; strcat(talker, "] ");
2755                     chattingPartner = p; break;
2756                     }
2757                 } else
2758                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2759                 for(p=0; p<MAX_CHAT; p++) {
2760                     if(!strcmp("WHISPER", chatPartner[p])) {
2761                         talker[0] = '['; strcat(talker, "] ");
2762                         chattingPartner = p; break;
2763                     }
2764                 }
2765                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2766                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2767                     talker[0] = 0;
2768                     chattingPartner = p; break;
2769                 }
2770                 if(chattingPartner<0) i = oldi; else {
2771                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2772                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2773                     started = STARTED_COMMENT;
2774                     parse_pos = 0; parse[0] = NULLCHAR;
2775                     savingComment = 3 + chattingPartner; // counts as TRUE
2776                     suppressKibitz = TRUE;
2777                     continue;
2778                 }
2779             } // [HGM] chat: end of patch
2780
2781             if (appData.zippyTalk || appData.zippyPlay) {
2782                 /* [DM] Backup address for color zippy lines */
2783                 backup = i;
2784 #if ZIPPY
2785        #ifdef WIN32
2786                if (loggedOn == TRUE)
2787                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2788                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2789        #else
2790                 if (ZippyControl(buf, &i) ||
2791                     ZippyConverse(buf, &i) ||
2792                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2793                       loggedOn = TRUE;
2794                       if (!appData.colorize) continue;
2795                 }
2796        #endif
2797 #endif
2798             } // [DM] 'else { ' deleted
2799                 if (
2800                     /* Regular tells and says */
2801                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2802                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2803                     looking_at(buf, &i, "* says: ") ||
2804                     /* Don't color "message" or "messages" output */
2805                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2806                     looking_at(buf, &i, "*. * at *:*: ") ||
2807                     looking_at(buf, &i, "--* (*:*): ") ||
2808                     /* Message notifications (same color as tells) */
2809                     looking_at(buf, &i, "* has left a message ") ||
2810                     looking_at(buf, &i, "* just sent you a message:\n") ||
2811                     /* Whispers and kibitzes */
2812                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2813                     looking_at(buf, &i, "* kibitzes: ") ||
2814                     /* Channel tells */
2815                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2816
2817                   if (tkind == 1 && strchr(star_match[0], ':')) {
2818                       /* Avoid "tells you:" spoofs in channels */
2819                      tkind = 3;
2820                   }
2821                   if (star_match[0][0] == NULLCHAR ||
2822                       strchr(star_match[0], ' ') ||
2823                       (tkind == 3 && strchr(star_match[1], ' '))) {
2824                     /* Reject bogus matches */
2825                     i = oldi;
2826                   } else {
2827                     if (appData.colorize) {
2828                       if (oldi > next_out) {
2829                         SendToPlayer(&buf[next_out], oldi - next_out);
2830                         next_out = oldi;
2831                       }
2832                       switch (tkind) {
2833                       case 1:
2834                         Colorize(ColorTell, FALSE);
2835                         curColor = ColorTell;
2836                         break;
2837                       case 2:
2838                         Colorize(ColorKibitz, FALSE);
2839                         curColor = ColorKibitz;
2840                         break;
2841                       case 3:
2842                         p = strrchr(star_match[1], '(');
2843                         if (p == NULL) {
2844                           p = star_match[1];
2845                         } else {
2846                           p++;
2847                         }
2848                         if (atoi(p) == 1) {
2849                           Colorize(ColorChannel1, FALSE);
2850                           curColor = ColorChannel1;
2851                         } else {
2852                           Colorize(ColorChannel, FALSE);
2853                           curColor = ColorChannel;
2854                         }
2855                         break;
2856                       case 5:
2857                         curColor = ColorNormal;
2858                         break;
2859                       }
2860                     }
2861                     if (started == STARTED_NONE && appData.autoComment &&
2862                         (gameMode == IcsObserving ||
2863                          gameMode == IcsPlayingWhite ||
2864                          gameMode == IcsPlayingBlack)) {
2865                       parse_pos = i - oldi;
2866                       memcpy(parse, &buf[oldi], parse_pos);
2867                       parse[parse_pos] = NULLCHAR;
2868                       started = STARTED_COMMENT;
2869                       savingComment = TRUE;
2870                     } else {
2871                       started = STARTED_CHATTER;
2872                       savingComment = FALSE;
2873                     }
2874                     loggedOn = TRUE;
2875                     continue;
2876                   }
2877                 }
2878
2879                 if (looking_at(buf, &i, "* s-shouts: ") ||
2880                     looking_at(buf, &i, "* c-shouts: ")) {
2881                     if (appData.colorize) {
2882                         if (oldi > next_out) {
2883                             SendToPlayer(&buf[next_out], oldi - next_out);
2884                             next_out = oldi;
2885                         }
2886                         Colorize(ColorSShout, FALSE);
2887                         curColor = ColorSShout;
2888                     }
2889                     loggedOn = TRUE;
2890                     started = STARTED_CHATTER;
2891                     continue;
2892                 }
2893
2894                 if (looking_at(buf, &i, "--->")) {
2895                     loggedOn = TRUE;
2896                     continue;
2897                 }
2898
2899                 if (looking_at(buf, &i, "* shouts: ") ||
2900                     looking_at(buf, &i, "--> ")) {
2901                     if (appData.colorize) {
2902                         if (oldi > next_out) {
2903                             SendToPlayer(&buf[next_out], oldi - next_out);
2904                             next_out = oldi;
2905                         }
2906                         Colorize(ColorShout, FALSE);
2907                         curColor = ColorShout;
2908                     }
2909                     loggedOn = TRUE;
2910                     started = STARTED_CHATTER;
2911                     continue;
2912                 }
2913
2914                 if (looking_at( buf, &i, "Challenge:")) {
2915                     if (appData.colorize) {
2916                         if (oldi > next_out) {
2917                             SendToPlayer(&buf[next_out], oldi - next_out);
2918                             next_out = oldi;
2919                         }
2920                         Colorize(ColorChallenge, FALSE);
2921                         curColor = ColorChallenge;
2922                     }
2923                     loggedOn = TRUE;
2924                     continue;
2925                 }
2926
2927                 if (looking_at(buf, &i, "* offers you") ||
2928                     looking_at(buf, &i, "* offers to be") ||
2929                     looking_at(buf, &i, "* would like to") ||
2930                     looking_at(buf, &i, "* requests to") ||
2931                     looking_at(buf, &i, "Your opponent offers") ||
2932                     looking_at(buf, &i, "Your opponent requests")) {
2933
2934                     if (appData.colorize) {
2935                         if (oldi > next_out) {
2936                             SendToPlayer(&buf[next_out], oldi - next_out);
2937                             next_out = oldi;
2938                         }
2939                         Colorize(ColorRequest, FALSE);
2940                         curColor = ColorRequest;
2941                     }
2942                     continue;
2943                 }
2944
2945                 if (looking_at(buf, &i, "* (*) seeking")) {
2946                     if (appData.colorize) {
2947                         if (oldi > next_out) {
2948                             SendToPlayer(&buf[next_out], oldi - next_out);
2949                             next_out = oldi;
2950                         }
2951                         Colorize(ColorSeek, FALSE);
2952                         curColor = ColorSeek;
2953                     }
2954                     continue;
2955             }
2956
2957             if (looking_at(buf, &i, "\\   ")) {
2958                 if (prevColor != ColorNormal) {
2959                     if (oldi > next_out) {
2960                         SendToPlayer(&buf[next_out], oldi - next_out);
2961                         next_out = oldi;
2962                     }
2963                     Colorize(prevColor, TRUE);
2964                     curColor = prevColor;
2965                 }
2966                 if (savingComment) {
2967                     parse_pos = i - oldi;
2968                     memcpy(parse, &buf[oldi], parse_pos);
2969                     parse[parse_pos] = NULLCHAR;
2970                     started = STARTED_COMMENT;
2971                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2972                         chattingPartner = savingComment - 3; // kludge to remember the box
2973                 } else {
2974                     started = STARTED_CHATTER;
2975                 }
2976                 continue;
2977             }
2978
2979             if (looking_at(buf, &i, "Black Strength :") ||
2980                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2981                 looking_at(buf, &i, "<10>") ||
2982                 looking_at(buf, &i, "#@#")) {
2983                 /* Wrong board style */
2984                 loggedOn = TRUE;
2985                 SendToICS(ics_prefix);
2986                 SendToICS("set style 12\n");
2987                 SendToICS(ics_prefix);
2988                 SendToICS("refresh\n");
2989                 continue;
2990             }
2991             
2992             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2993                 ICSInitScript();
2994                 have_sent_ICS_logon = 1;
2995                 continue;
2996             }
2997               
2998             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2999                 (looking_at(buf, &i, "\n<12> ") ||
3000                  looking_at(buf, &i, "<12> "))) {
3001                 loggedOn = TRUE;
3002                 if (oldi > next_out) {
3003                     SendToPlayer(&buf[next_out], oldi - next_out);
3004                 }
3005                 next_out = i;
3006                 started = STARTED_BOARD;
3007                 parse_pos = 0;
3008                 continue;
3009             }
3010
3011             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3012                 looking_at(buf, &i, "<b1> ")) {
3013                 if (oldi > next_out) {
3014                     SendToPlayer(&buf[next_out], oldi - next_out);
3015                 }
3016                 next_out = i;
3017                 started = STARTED_HOLDINGS;
3018                 parse_pos = 0;
3019                 continue;
3020             }
3021
3022             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3023                 loggedOn = TRUE;
3024                 /* Header for a move list -- first line */
3025
3026                 switch (ics_getting_history) {
3027                   case H_FALSE:
3028                     switch (gameMode) {
3029                       case IcsIdle:
3030                       case BeginningOfGame:
3031                         /* User typed "moves" or "oldmoves" while we
3032                            were idle.  Pretend we asked for these
3033                            moves and soak them up so user can step
3034                            through them and/or save them.
3035                            */
3036                         Reset(FALSE, TRUE);
3037                         gameMode = IcsObserving;
3038                         ModeHighlight();
3039                         ics_gamenum = -1;
3040                         ics_getting_history = H_GOT_UNREQ_HEADER;
3041                         break;
3042                       case EditGame: /*?*/
3043                       case EditPosition: /*?*/
3044                         /* Should above feature work in these modes too? */
3045                         /* For now it doesn't */
3046                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3047                         break;
3048                       default:
3049                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3050                         break;
3051                     }
3052                     break;
3053                   case H_REQUESTED:
3054                     /* Is this the right one? */
3055                     if (gameInfo.white && gameInfo.black &&
3056                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3057                         strcmp(gameInfo.black, star_match[2]) == 0) {
3058                         /* All is well */
3059                         ics_getting_history = H_GOT_REQ_HEADER;
3060                     }
3061                     break;
3062                   case H_GOT_REQ_HEADER:
3063                   case H_GOT_UNREQ_HEADER:
3064                   case H_GOT_UNWANTED_HEADER:
3065                   case H_GETTING_MOVES:
3066                     /* Should not happen */
3067                     DisplayError(_("Error gathering move list: two headers"), 0);
3068                     ics_getting_history = H_FALSE;
3069                     break;
3070                 }
3071
3072                 /* Save player ratings into gameInfo if needed */
3073                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3074                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3075                     (gameInfo.whiteRating == -1 ||
3076                      gameInfo.blackRating == -1)) {
3077
3078                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3079                     gameInfo.blackRating = string_to_rating(star_match[3]);
3080                     if (appData.debugMode)
3081                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3082                               gameInfo.whiteRating, gameInfo.blackRating);
3083                 }
3084                 continue;
3085             }
3086
3087             if (looking_at(buf, &i,
3088               "* * match, initial time: * minute*, increment: * second")) {
3089                 /* Header for a move list -- second line */
3090                 /* Initial board will follow if this is a wild game */
3091                 if (gameInfo.event != NULL) free(gameInfo.event);
3092                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3093                 gameInfo.event = StrSave(str);
3094                 /* [HGM] we switched variant. Translate boards if needed. */
3095                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3096                 continue;
3097             }
3098
3099             if (looking_at(buf, &i, "Move  ")) {
3100                 /* Beginning of a move list */
3101                 switch (ics_getting_history) {
3102                   case H_FALSE:
3103                     /* Normally should not happen */
3104                     /* Maybe user hit reset while we were parsing */
3105                     break;
3106                   case H_REQUESTED:
3107                     /* Happens if we are ignoring a move list that is not
3108                      * the one we just requested.  Common if the user
3109                      * tries to observe two games without turning off
3110                      * getMoveList */
3111                     break;
3112                   case H_GETTING_MOVES:
3113                     /* Should not happen */
3114                     DisplayError(_("Error gathering move list: nested"), 0);
3115                     ics_getting_history = H_FALSE;
3116                     break;
3117                   case H_GOT_REQ_HEADER:
3118                     ics_getting_history = H_GETTING_MOVES;
3119                     started = STARTED_MOVES;
3120                     parse_pos = 0;
3121                     if (oldi > next_out) {
3122                         SendToPlayer(&buf[next_out], oldi - next_out);
3123                     }
3124                     break;
3125                   case H_GOT_UNREQ_HEADER:
3126                     ics_getting_history = H_GETTING_MOVES;
3127                     started = STARTED_MOVES_NOHIDE;
3128                     parse_pos = 0;
3129                     break;
3130                   case H_GOT_UNWANTED_HEADER:
3131                     ics_getting_history = H_FALSE;
3132                     break;
3133                 }
3134                 continue;
3135             }                           
3136             
3137             if (looking_at(buf, &i, "% ") ||
3138                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3139                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3140                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3141                     soughtPending = FALSE;
3142                     seekGraphUp = TRUE;
3143                     DrawSeekGraph();
3144                 }
3145                 if(suppressKibitz) next_out = i;
3146                 savingComment = FALSE;
3147                 suppressKibitz = 0;
3148                 switch (started) {
3149                   case STARTED_MOVES:
3150                   case STARTED_MOVES_NOHIDE:
3151                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3152                     parse[parse_pos + i - oldi] = NULLCHAR;
3153                     ParseGameHistory(parse);
3154 #if ZIPPY
3155                     if (appData.zippyPlay && first.initDone) {
3156                         FeedMovesToProgram(&first, forwardMostMove);
3157                         if (gameMode == IcsPlayingWhite) {
3158                             if (WhiteOnMove(forwardMostMove)) {
3159                                 if (first.sendTime) {
3160                                   if (first.useColors) {
3161                                     SendToProgram("black\n", &first); 
3162                                   }
3163                                   SendTimeRemaining(&first, TRUE);
3164                                 }
3165                                 if (first.useColors) {
3166                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3167                                 }
3168                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3169                                 first.maybeThinking = TRUE;
3170                             } else {
3171                                 if (first.usePlayother) {
3172                                   if (first.sendTime) {
3173                                     SendTimeRemaining(&first, TRUE);
3174                                   }
3175                                   SendToProgram("playother\n", &first);
3176                                   firstMove = FALSE;
3177                                 } else {
3178                                   firstMove = TRUE;
3179                                 }
3180                             }
3181                         } else if (gameMode == IcsPlayingBlack) {
3182                             if (!WhiteOnMove(forwardMostMove)) {
3183                                 if (first.sendTime) {
3184                                   if (first.useColors) {
3185                                     SendToProgram("white\n", &first);
3186                                   }
3187                                   SendTimeRemaining(&first, FALSE);
3188                                 }
3189                                 if (first.useColors) {
3190                                   SendToProgram("black\n", &first);
3191                                 }
3192                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3193                                 first.maybeThinking = TRUE;
3194                             } else {
3195                                 if (first.usePlayother) {
3196                                   if (first.sendTime) {
3197                                     SendTimeRemaining(&first, FALSE);
3198                                   }
3199                                   SendToProgram("playother\n", &first);
3200                                   firstMove = FALSE;
3201                                 } else {
3202                                   firstMove = TRUE;
3203                                 }
3204                             }
3205                         }                       
3206                     }
3207 #endif
3208                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3209                         /* Moves came from oldmoves or moves command
3210                            while we weren't doing anything else.
3211                            */
3212                         currentMove = forwardMostMove;
3213                         ClearHighlights();/*!!could figure this out*/
3214                         flipView = appData.flipView;
3215                         DrawPosition(TRUE, boards[currentMove]);
3216                         DisplayBothClocks();
3217                         sprintf(str, "%s vs. %s",
3218                                 gameInfo.white, gameInfo.black);
3219                         DisplayTitle(str);
3220                         gameMode = IcsIdle;
3221                     } else {
3222                         /* Moves were history of an active game */
3223                         if (gameInfo.resultDetails != NULL) {
3224                             free(gameInfo.resultDetails);
3225                             gameInfo.resultDetails = NULL;
3226                         }
3227                     }
3228                     HistorySet(parseList, backwardMostMove,
3229                                forwardMostMove, currentMove-1);
3230                     DisplayMove(currentMove - 1);
3231                     if (started == STARTED_MOVES) next_out = i;
3232                     started = STARTED_NONE;
3233                     ics_getting_history = H_FALSE;
3234                     break;
3235
3236                   case STARTED_OBSERVE:
3237                     started = STARTED_NONE;
3238                     SendToICS(ics_prefix);
3239                     SendToICS("refresh\n");
3240                     break;
3241
3242                   default:
3243                     break;
3244                 }
3245                 if(bookHit) { // [HGM] book: simulate book reply
3246                     static char bookMove[MSG_SIZ]; // a bit generous?
3247
3248                     programStats.nodes = programStats.depth = programStats.time = 
3249                     programStats.score = programStats.got_only_move = 0;
3250                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3251
3252                     strcpy(bookMove, "move ");
3253                     strcat(bookMove, bookHit);
3254                     HandleMachineMove(bookMove, &first);
3255                 }
3256                 continue;
3257             }
3258             
3259             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3260                  started == STARTED_HOLDINGS ||
3261                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3262                 /* Accumulate characters in move list or board */
3263                 parse[parse_pos++] = buf[i];
3264             }
3265             
3266             /* Start of game messages.  Mostly we detect start of game
3267                when the first board image arrives.  On some versions
3268                of the ICS, though, we need to do a "refresh" after starting
3269                to observe in order to get the current board right away. */
3270             if (looking_at(buf, &i, "Adding game * to observation list")) {
3271                 started = STARTED_OBSERVE;
3272                 continue;
3273             }
3274
3275             /* Handle auto-observe */
3276             if (appData.autoObserve &&
3277                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3278                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3279                 char *player;
3280                 /* Choose the player that was highlighted, if any. */
3281                 if (star_match[0][0] == '\033' ||
3282                     star_match[1][0] != '\033') {
3283                     player = star_match[0];
3284                 } else {
3285                     player = star_match[2];
3286                 }
3287                 sprintf(str, "%sobserve %s\n",
3288                         ics_prefix, StripHighlightAndTitle(player));
3289                 SendToICS(str);
3290
3291                 /* Save ratings from notify string */
3292                 strcpy(player1Name, star_match[0]);
3293                 player1Rating = string_to_rating(star_match[1]);
3294                 strcpy(player2Name, star_match[2]);
3295                 player2Rating = string_to_rating(star_match[3]);
3296
3297                 if (appData.debugMode)
3298                   fprintf(debugFP, 
3299                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3300                           player1Name, player1Rating,
3301                           player2Name, player2Rating);
3302
3303                 continue;
3304             }
3305
3306             /* Deal with automatic examine mode after a game,
3307                and with IcsObserving -> IcsExamining transition */
3308             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3309                 looking_at(buf, &i, "has made you an examiner of game *")) {
3310
3311                 int gamenum = atoi(star_match[0]);
3312                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3313                     gamenum == ics_gamenum) {
3314                     /* We were already playing or observing this game;
3315                        no need to refetch history */
3316                     gameMode = IcsExamining;
3317                     if (pausing) {
3318                         pauseExamForwardMostMove = forwardMostMove;
3319                     } else if (currentMove < forwardMostMove) {
3320                         ForwardInner(forwardMostMove);
3321                     }
3322                 } else {
3323                     /* I don't think this case really can happen */
3324                     SendToICS(ics_prefix);
3325                     SendToICS("refresh\n");
3326                 }
3327                 continue;
3328             }    
3329             
3330             /* Error messages */
3331 //          if (ics_user_moved) {
3332             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3333                 if (looking_at(buf, &i, "Illegal move") ||
3334                     looking_at(buf, &i, "Not a legal move") ||
3335                     looking_at(buf, &i, "Your king is in check") ||
3336                     looking_at(buf, &i, "It isn't your turn") ||
3337                     looking_at(buf, &i, "It is not your move")) {
3338                     /* Illegal move */
3339                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3340                         currentMove = forwardMostMove-1;
3341                         DisplayMove(currentMove - 1); /* before DMError */
3342                         DrawPosition(FALSE, boards[currentMove]);
3343                         SwitchClocks(forwardMostMove-1); // [HGM] race
3344                         DisplayBothClocks();
3345                     }
3346                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3347                     ics_user_moved = 0;
3348                     continue;
3349                 }
3350             }
3351
3352             if (looking_at(buf, &i, "still have time") ||
3353                 looking_at(buf, &i, "not out of time") ||
3354                 looking_at(buf, &i, "either player is out of time") ||
3355                 looking_at(buf, &i, "has timeseal; checking")) {
3356                 /* We must have called his flag a little too soon */
3357                 whiteFlag = blackFlag = FALSE;
3358                 continue;
3359             }
3360
3361             if (looking_at(buf, &i, "added * seconds to") ||
3362                 looking_at(buf, &i, "seconds were added to")) {
3363                 /* Update the clocks */
3364                 SendToICS(ics_prefix);
3365                 SendToICS("refresh\n");
3366                 continue;
3367             }
3368
3369             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3370                 ics_clock_paused = TRUE;
3371                 StopClocks();
3372                 continue;
3373             }
3374
3375             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3376                 ics_clock_paused = FALSE;
3377                 StartClocks();
3378                 continue;
3379             }
3380
3381             /* Grab player ratings from the Creating: message.
3382                Note we have to check for the special case when
3383                the ICS inserts things like [white] or [black]. */
3384             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3385                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3386                 /* star_matches:
3387                    0    player 1 name (not necessarily white)
3388                    1    player 1 rating
3389                    2    empty, white, or black (IGNORED)
3390                    3    player 2 name (not necessarily black)
3391                    4    player 2 rating
3392                    
3393                    The names/ratings are sorted out when the game
3394                    actually starts (below).
3395                 */
3396                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3397                 player1Rating = string_to_rating(star_match[1]);
3398                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3399                 player2Rating = string_to_rating(star_match[4]);
3400
3401                 if (appData.debugMode)
3402                   fprintf(debugFP, 
3403                           "Ratings from 'Creating:' %s %d, %s %d\n",
3404                           player1Name, player1Rating,
3405                           player2Name, player2Rating);
3406
3407                 continue;
3408             }
3409             
3410             /* Improved generic start/end-of-game messages */
3411             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3412                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3413                 /* If tkind == 0: */
3414                 /* star_match[0] is the game number */
3415                 /*           [1] is the white player's name */
3416                 /*           [2] is the black player's name */
3417                 /* For end-of-game: */
3418                 /*           [3] is the reason for the game end */
3419                 /*           [4] is a PGN end game-token, preceded by " " */
3420                 /* For start-of-game: */
3421                 /*           [3] begins with "Creating" or "Continuing" */
3422                 /*           [4] is " *" or empty (don't care). */
3423                 int gamenum = atoi(star_match[0]);
3424                 char *whitename, *blackname, *why, *endtoken;
3425                 ChessMove endtype = (ChessMove) 0;
3426
3427                 if (tkind == 0) {
3428                   whitename = star_match[1];
3429                   blackname = star_match[2];
3430                   why = star_match[3];
3431                   endtoken = star_match[4];
3432                 } else {
3433                   whitename = star_match[1];
3434                   blackname = star_match[3];
3435                   why = star_match[5];
3436                   endtoken = star_match[6];
3437                 }
3438
3439                 /* Game start messages */
3440                 if (strncmp(why, "Creating ", 9) == 0 ||
3441                     strncmp(why, "Continuing ", 11) == 0) {
3442                     gs_gamenum = gamenum;
3443                     strcpy(gs_kind, strchr(why, ' ') + 1);
3444                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3445 #if ZIPPY
3446                     if (appData.zippyPlay) {
3447                         ZippyGameStart(whitename, blackname);
3448                     }
3449 #endif /*ZIPPY*/
3450                     continue;
3451                 }
3452
3453                 /* Game end messages */
3454                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3455                     ics_gamenum != gamenum) {
3456                     continue;
3457                 }
3458                 while (endtoken[0] == ' ') endtoken++;
3459                 switch (endtoken[0]) {
3460                   case '*':
3461                   default:
3462                     endtype = GameUnfinished;
3463                     break;
3464                   case '0':
3465                     endtype = BlackWins;
3466                     break;
3467                   case '1':
3468                     if (endtoken[1] == '/')
3469                       endtype = GameIsDrawn;
3470                     else
3471                       endtype = WhiteWins;
3472                     break;
3473                 }
3474                 GameEnds(endtype, why, GE_ICS);
3475 #if ZIPPY
3476                 if (appData.zippyPlay && first.initDone) {
3477                     ZippyGameEnd(endtype, why);
3478                     if (first.pr == NULL) {
3479                       /* Start the next process early so that we'll
3480                          be ready for the next challenge */
3481                       StartChessProgram(&first);
3482                     }
3483                     /* Send "new" early, in case this command takes
3484                        a long time to finish, so that we'll be ready
3485                        for the next challenge. */
3486                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3487                     Reset(TRUE, TRUE);
3488                 }
3489 #endif /*ZIPPY*/
3490                 continue;
3491             }
3492
3493             if (looking_at(buf, &i, "Removing game * from observation") ||
3494                 looking_at(buf, &i, "no longer observing game *") ||
3495                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3496                 if (gameMode == IcsObserving &&
3497                     atoi(star_match[0]) == ics_gamenum)
3498                   {
3499                       /* icsEngineAnalyze */
3500                       if (appData.icsEngineAnalyze) {
3501                             ExitAnalyzeMode();
3502                             ModeHighlight();
3503                       }
3504                       StopClocks();
3505                       gameMode = IcsIdle;
3506                       ics_gamenum = -1;
3507                       ics_user_moved = FALSE;
3508                   }
3509                 continue;
3510             }
3511
3512             if (looking_at(buf, &i, "no longer examining game *")) {
3513                 if (gameMode == IcsExamining &&
3514                     atoi(star_match[0]) == ics_gamenum)
3515                   {
3516                       gameMode = IcsIdle;
3517                       ics_gamenum = -1;
3518                       ics_user_moved = FALSE;
3519                   }
3520                 continue;
3521             }
3522
3523             /* Advance leftover_start past any newlines we find,
3524                so only partial lines can get reparsed */
3525             if (looking_at(buf, &i, "\n")) {
3526                 prevColor = curColor;
3527                 if (curColor != ColorNormal) {
3528                     if (oldi > next_out) {
3529                         SendToPlayer(&buf[next_out], oldi - next_out);
3530                         next_out = oldi;
3531                     }
3532                     Colorize(ColorNormal, FALSE);
3533                     curColor = ColorNormal;
3534                 }
3535                 if (started == STARTED_BOARD) {
3536                     started = STARTED_NONE;
3537                     parse[parse_pos] = NULLCHAR;
3538                     ParseBoard12(parse);
3539                     ics_user_moved = 0;
3540
3541                     /* Send premove here */
3542                     if (appData.premove) {
3543                       char str[MSG_SIZ];
3544                       if (currentMove == 0 &&
3545                           gameMode == IcsPlayingWhite &&
3546                           appData.premoveWhite) {
3547                         sprintf(str, "%s\n", appData.premoveWhiteText);
3548                         if (appData.debugMode)
3549                           fprintf(debugFP, "Sending premove:\n");
3550                         SendToICS(str);
3551                       } else if (currentMove == 1 &&
3552                                  gameMode == IcsPlayingBlack &&
3553                                  appData.premoveBlack) {
3554                         sprintf(str, "%s\n", appData.premoveBlackText);
3555                         if (appData.debugMode)
3556                           fprintf(debugFP, "Sending premove:\n");
3557                         SendToICS(str);
3558                       } else if (gotPremove) {
3559                         gotPremove = 0;
3560                         ClearPremoveHighlights();
3561                         if (appData.debugMode)
3562                           fprintf(debugFP, "Sending premove:\n");
3563                           UserMoveEvent(premoveFromX, premoveFromY, 
3564                                         premoveToX, premoveToY, 
3565                                         premovePromoChar);
3566                       }
3567                     }
3568
3569                     /* Usually suppress following prompt */
3570                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3571                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3572                         if (looking_at(buf, &i, "*% ")) {
3573                             savingComment = FALSE;
3574                             suppressKibitz = 0;
3575                         }
3576                     }
3577                     next_out = i;
3578                 } else if (started == STARTED_HOLDINGS) {
3579                     int gamenum;
3580                     char new_piece[MSG_SIZ];
3581                     started = STARTED_NONE;
3582                     parse[parse_pos] = NULLCHAR;
3583                     if (appData.debugMode)
3584                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3585                                                         parse, currentMove);
3586                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3587                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3588                         if (gameInfo.variant == VariantNormal) {
3589                           /* [HGM] We seem to switch variant during a game!
3590                            * Presumably no holdings were displayed, so we have
3591                            * to move the position two files to the right to
3592                            * create room for them!
3593                            */
3594                           VariantClass newVariant;
3595                           switch(gameInfo.boardWidth) { // base guess on board width
3596                                 case 9:  newVariant = VariantShogi; break;
3597                                 case 10: newVariant = VariantGreat; break;
3598                                 default: newVariant = VariantCrazyhouse; break;
3599                           }
3600                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3601                           /* Get a move list just to see the header, which
3602                              will tell us whether this is really bug or zh */
3603                           if (ics_getting_history == H_FALSE) {
3604                             ics_getting_history = H_REQUESTED;
3605                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3606                             SendToICS(str);
3607                           }
3608                         }
3609                         new_piece[0] = NULLCHAR;
3610                         sscanf(parse, "game %d white [%s black [%s <- %s",
3611                                &gamenum, white_holding, black_holding,
3612                                new_piece);
3613                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3614                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3615                         /* [HGM] copy holdings to board holdings area */
3616                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3617                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3618                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3619 #if ZIPPY
3620                         if (appData.zippyPlay && first.initDone) {
3621                             ZippyHoldings(white_holding, black_holding,
3622                                           new_piece);
3623                         }
3624 #endif /*ZIPPY*/
3625                         if (tinyLayout || smallLayout) {
3626                             char wh[16], bh[16];
3627                             PackHolding(wh, white_holding);
3628                             PackHolding(bh, black_holding);
3629                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3630                                     gameInfo.white, gameInfo.black);
3631                         } else {
3632                             sprintf(str, "%s [%s] vs. %s [%s]",
3633                                     gameInfo.white, white_holding,
3634                                     gameInfo.black, black_holding);
3635                         }
3636
3637                         DrawPosition(FALSE, boards[currentMove]);
3638                         DisplayTitle(str);
3639                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3640                         sscanf(parse, "game %d white [%s black [%s <- %s",
3641                                &gamenum, white_holding, black_holding,
3642                                new_piece);
3643                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3644                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3645                         /* [HGM] copy holdings to partner-board holdings area */
3646                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3647                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3648                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3649                       }
3650                     }
3651                     /* Suppress following prompt */
3652                     if (looking_at(buf, &i, "*% ")) {
3653                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3654                         savingComment = FALSE;
3655                         suppressKibitz = 0;
3656                     }
3657                     next_out = i;
3658                 }
3659                 continue;
3660             }
3661
3662             i++;                /* skip unparsed character and loop back */
3663         }
3664         
3665         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3666 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3667 //          SendToPlayer(&buf[next_out], i - next_out);
3668             started != STARTED_HOLDINGS && leftover_start > next_out) {
3669             SendToPlayer(&buf[next_out], leftover_start - next_out);
3670             next_out = i;
3671         }
3672         
3673         leftover_len = buf_len - leftover_start;
3674         /* if buffer ends with something we couldn't parse,
3675            reparse it after appending the next read */
3676         
3677     } else if (count == 0) {
3678         RemoveInputSource(isr);
3679         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3680     } else {
3681         DisplayFatalError(_("Error reading from ICS"), error, 1);
3682     }
3683 }
3684
3685
3686 /* Board style 12 looks like this:
3687    
3688    <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
3689    
3690  * The "<12> " is stripped before it gets to this routine.  The two
3691  * trailing 0's (flip state and clock ticking) are later addition, and
3692  * some chess servers may not have them, or may have only the first.
3693  * Additional trailing fields may be added in the future.  
3694  */
3695
3696 #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"
3697
3698 #define RELATION_OBSERVING_PLAYED    0
3699 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3700 #define RELATION_PLAYING_MYMOVE      1
3701 #define RELATION_PLAYING_NOTMYMOVE  -1
3702 #define RELATION_EXAMINING           2
3703 #define RELATION_ISOLATED_BOARD     -3
3704 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3705
3706 void
3707 ParseBoard12(string)
3708      char *string;
3709
3710     GameMode newGameMode;
3711     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3712     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3713     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3714     char to_play, board_chars[200];
3715     char move_str[500], str[500], elapsed_time[500];
3716     char black[32], white[32];
3717     Board board;
3718     int prevMove = currentMove;
3719     int ticking = 2;
3720     ChessMove moveType;
3721     int fromX, fromY, toX, toY;
3722     char promoChar;
3723     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3724     char *bookHit = NULL; // [HGM] book
3725     Boolean weird = FALSE, reqFlag = FALSE;
3726
3727     fromX = fromY = toX = toY = -1;
3728     
3729     newGame = FALSE;
3730
3731     if (appData.debugMode)
3732       fprintf(debugFP, _("Parsing board: %s\n"), string);
3733
3734     move_str[0] = NULLCHAR;
3735     elapsed_time[0] = NULLCHAR;
3736     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3737         int  i = 0, j;
3738         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3739             if(string[i] == ' ') { ranks++; files = 0; }
3740             else files++;
3741             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3742             i++;
3743         }
3744         for(j = 0; j <i; j++) board_chars[j] = string[j];
3745         board_chars[i] = '\0';
3746         string += i + 1;
3747     }
3748     n = sscanf(string, PATTERN, &to_play, &double_push,
3749                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3750                &gamenum, white, black, &relation, &basetime, &increment,
3751                &white_stren, &black_stren, &white_time, &black_time,
3752                &moveNum, str, elapsed_time, move_str, &ics_flip,
3753                &ticking);
3754
3755     if (n < 21) {
3756         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3757         DisplayError(str, 0);
3758         return;
3759     }
3760
3761     /* Convert the move number to internal form */
3762     moveNum = (moveNum - 1) * 2;
3763     if (to_play == 'B') moveNum++;
3764     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3765       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3766                         0, 1);
3767       return;
3768     }
3769     
3770     switch (relation) {
3771       case RELATION_OBSERVING_PLAYED:
3772       case RELATION_OBSERVING_STATIC:
3773         if (gamenum == -1) {
3774             /* Old ICC buglet */
3775             relation = RELATION_OBSERVING_STATIC;
3776         }
3777         newGameMode = IcsObserving;
3778         break;
3779       case RELATION_PLAYING_MYMOVE:
3780       case RELATION_PLAYING_NOTMYMOVE:
3781         newGameMode =
3782           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3783             IcsPlayingWhite : IcsPlayingBlack;
3784         break;
3785       case RELATION_EXAMINING:
3786         newGameMode = IcsExamining;
3787         break;
3788       case RELATION_ISOLATED_BOARD:
3789       default:
3790         /* Just display this board.  If user was doing something else,
3791            we will forget about it until the next board comes. */ 
3792         newGameMode = IcsIdle;
3793         break;
3794       case RELATION_STARTING_POSITION:
3795         newGameMode = gameMode;
3796         break;
3797     }
3798     
3799     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3800          && newGameMode == IcsObserving && appData.bgObserve) {
3801       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3802       char buf[MSG_SIZ];
3803       for (k = 0; k < ranks; k++) {
3804         for (j = 0; j < files; j++)
3805           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3806         if(gameInfo.holdingsWidth > 1) {
3807              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3808              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3809         }
3810       }
3811       CopyBoard(partnerBoard, board);
3812       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3813       sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3814                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3815       DisplayMessage(buf, "");
3816       return;
3817     }
3818
3819     /* Modify behavior for initial board display on move listing
3820        of wild games.
3821        */
3822     switch (ics_getting_history) {
3823       case H_FALSE:
3824       case H_REQUESTED:
3825         break;
3826       case H_GOT_REQ_HEADER:
3827       case H_GOT_UNREQ_HEADER:
3828         /* This is the initial position of the current game */
3829         gamenum = ics_gamenum;
3830         moveNum = 0;            /* old ICS bug workaround */
3831         if (to_play == 'B') {
3832           startedFromSetupPosition = TRUE;
3833           blackPlaysFirst = TRUE;
3834           moveNum = 1;
3835           if (forwardMostMove == 0) forwardMostMove = 1;
3836           if (backwardMostMove == 0) backwardMostMove = 1;
3837           if (currentMove == 0) currentMove = 1;
3838         }
3839         newGameMode = gameMode;
3840         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3841         break;
3842       case H_GOT_UNWANTED_HEADER:
3843         /* This is an initial board that we don't want */
3844         return;
3845       case H_GETTING_MOVES:
3846         /* Should not happen */
3847         DisplayError(_("Error gathering move list: extra board"), 0);
3848         ics_getting_history = H_FALSE;
3849         return;
3850     }
3851
3852    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3853                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3854      /* [HGM] We seem to have switched variant unexpectedly
3855       * Try to guess new variant from board size
3856       */
3857           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3858           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3859           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3860           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3861           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3862           if(!weird) newVariant = VariantNormal;
3863           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3864           /* Get a move list just to see the header, which
3865              will tell us whether this is really bug or zh */
3866           if (ics_getting_history == H_FALSE) {
3867             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3868             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3869             SendToICS(str);
3870           }
3871     }
3872     
3873     /* Take action if this is the first board of a new game, or of a
3874        different game than is currently being displayed.  */
3875     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3876         relation == RELATION_ISOLATED_BOARD) {
3877         
3878         /* Forget the old game and get the history (if any) of the new one */
3879         if (gameMode != BeginningOfGame) {
3880           Reset(TRUE, TRUE);
3881         }
3882         newGame = TRUE;
3883         if (appData.autoRaiseBoard) BoardToTop();
3884         prevMove = -3;
3885         if (gamenum == -1) {
3886             newGameMode = IcsIdle;
3887         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3888                    appData.getMoveList && !reqFlag) {
3889             /* Need to get game history */
3890             ics_getting_history = H_REQUESTED;
3891             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3892             SendToICS(str);
3893         }
3894         
3895         /* Initially flip the board to have black on the bottom if playing
3896            black or if the ICS flip flag is set, but let the user change
3897            it with the Flip View button. */
3898         flipView = appData.autoFlipView ? 
3899           (newGameMode == IcsPlayingBlack) || ics_flip :
3900           appData.flipView;
3901         
3902         /* Done with values from previous mode; copy in new ones */
3903         gameMode = newGameMode;
3904         ModeHighlight();
3905         ics_gamenum = gamenum;
3906         if (gamenum == gs_gamenum) {
3907             int klen = strlen(gs_kind);
3908             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3909             sprintf(str, "ICS %s", gs_kind);
3910             gameInfo.event = StrSave(str);
3911         } else {
3912             gameInfo.event = StrSave("ICS game");
3913         }
3914         gameInfo.site = StrSave(appData.icsHost);
3915         gameInfo.date = PGNDate();
3916         gameInfo.round = StrSave("-");
3917         gameInfo.white = StrSave(white);
3918         gameInfo.black = StrSave(black);
3919         timeControl = basetime * 60 * 1000;
3920         timeControl_2 = 0;
3921         timeIncrement = increment * 1000;
3922         movesPerSession = 0;
3923         gameInfo.timeControl = TimeControlTagValue();
3924         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3925   if (appData.debugMode) {
3926     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3927     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3928     setbuf(debugFP, NULL);
3929   }
3930
3931         gameInfo.outOfBook = NULL;
3932         
3933         /* Do we have the ratings? */
3934         if (strcmp(player1Name, white) == 0 &&
3935             strcmp(player2Name, black) == 0) {
3936             if (appData.debugMode)
3937               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3938                       player1Rating, player2Rating);
3939             gameInfo.whiteRating = player1Rating;
3940             gameInfo.blackRating = player2Rating;
3941         } else if (strcmp(player2Name, white) == 0 &&
3942                    strcmp(player1Name, black) == 0) {
3943             if (appData.debugMode)
3944               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3945                       player2Rating, player1Rating);
3946             gameInfo.whiteRating = player2Rating;
3947             gameInfo.blackRating = player1Rating;
3948         }
3949         player1Name[0] = player2Name[0] = NULLCHAR;
3950
3951         /* Silence shouts if requested */
3952         if (appData.quietPlay &&
3953             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3954             SendToICS(ics_prefix);
3955             SendToICS("set shout 0\n");
3956         }
3957     }
3958     
3959     /* Deal with midgame name changes */
3960     if (!newGame) {
3961         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3962             if (gameInfo.white) free(gameInfo.white);
3963             gameInfo.white = StrSave(white);
3964         }
3965         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3966             if (gameInfo.black) free(gameInfo.black);
3967             gameInfo.black = StrSave(black);
3968         }
3969     }
3970     
3971     /* Throw away game result if anything actually changes in examine mode */
3972     if (gameMode == IcsExamining && !newGame) {
3973         gameInfo.result = GameUnfinished;
3974         if (gameInfo.resultDetails != NULL) {
3975             free(gameInfo.resultDetails);
3976             gameInfo.resultDetails = NULL;
3977         }
3978     }
3979     
3980     /* In pausing && IcsExamining mode, we ignore boards coming
3981        in if they are in a different variation than we are. */
3982     if (pauseExamInvalid) return;
3983     if (pausing && gameMode == IcsExamining) {
3984         if (moveNum <= pauseExamForwardMostMove) {
3985             pauseExamInvalid = TRUE;
3986             forwardMostMove = pauseExamForwardMostMove;
3987             return;
3988         }
3989     }
3990     
3991   if (appData.debugMode) {
3992     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3993   }
3994     /* Parse the board */
3995     for (k = 0; k < ranks; k++) {
3996       for (j = 0; j < files; j++)
3997         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3998       if(gameInfo.holdingsWidth > 1) {
3999            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4000            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4001       }
4002     }
4003     CopyBoard(boards[moveNum], board);
4004     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4005     if (moveNum == 0) {
4006         startedFromSetupPosition =
4007           !CompareBoards(board, initialPosition);
4008         if(startedFromSetupPosition)
4009             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4010     }
4011
4012     /* [HGM] Set castling rights. Take the outermost Rooks,
4013        to make it also work for FRC opening positions. Note that board12
4014        is really defective for later FRC positions, as it has no way to
4015        indicate which Rook can castle if they are on the same side of King.
4016        For the initial position we grant rights to the outermost Rooks,
4017        and remember thos rights, and we then copy them on positions
4018        later in an FRC game. This means WB might not recognize castlings with
4019        Rooks that have moved back to their original position as illegal,
4020        but in ICS mode that is not its job anyway.
4021     */
4022     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4023     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4024
4025         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4026             if(board[0][i] == WhiteRook) j = i;
4027         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4028         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4029             if(board[0][i] == WhiteRook) j = i;
4030         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4031         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4032             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4033         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4034         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4035             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4036         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4037
4038         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4039         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4040             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4041         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4042             if(board[BOARD_HEIGHT-1][k] == bKing)
4043                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4044         if(gameInfo.variant == VariantTwoKings) {
4045             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4046             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4047             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4048         }
4049     } else { int r;
4050         r = boards[moveNum][CASTLING][0] = initialRights[0];
4051         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4052         r = boards[moveNum][CASTLING][1] = initialRights[1];
4053         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4054         r = boards[moveNum][CASTLING][3] = initialRights[3];
4055         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4056         r = boards[moveNum][CASTLING][4] = initialRights[4];
4057         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4058         /* wildcastle kludge: always assume King has rights */
4059         r = boards[moveNum][CASTLING][2] = initialRights[2];
4060         r = boards[moveNum][CASTLING][5] = initialRights[5];
4061     }
4062     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4063     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4064
4065     
4066     if (ics_getting_history == H_GOT_REQ_HEADER ||
4067         ics_getting_history == H_GOT_UNREQ_HEADER) {
4068         /* This was an initial position from a move list, not
4069            the current position */
4070         return;
4071     }
4072     
4073     /* Update currentMove and known move number limits */
4074     newMove = newGame || moveNum > forwardMostMove;
4075
4076     if (newGame) {
4077         forwardMostMove = backwardMostMove = currentMove = moveNum;
4078         if (gameMode == IcsExamining && moveNum == 0) {
4079           /* Workaround for ICS limitation: we are not told the wild
4080              type when starting to examine a game.  But if we ask for
4081              the move list, the move list header will tell us */
4082             ics_getting_history = H_REQUESTED;
4083             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4084             SendToICS(str);
4085         }
4086     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4087                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4088 #if ZIPPY
4089         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4090         /* [HGM] applied this also to an engine that is silently watching        */
4091         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4092             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4093             gameInfo.variant == currentlyInitializedVariant) {
4094           takeback = forwardMostMove - moveNum;
4095           for (i = 0; i < takeback; i++) {
4096             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4097             SendToProgram("undo\n", &first);
4098           }
4099         }
4100 #endif
4101
4102         forwardMostMove = moveNum;
4103         if (!pausing || currentMove > forwardMostMove)
4104           currentMove = forwardMostMove;
4105     } else {
4106         /* New part of history that is not contiguous with old part */ 
4107         if (pausing && gameMode == IcsExamining) {
4108             pauseExamInvalid = TRUE;
4109             forwardMostMove = pauseExamForwardMostMove;
4110             return;
4111         }
4112         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4113 #if ZIPPY
4114             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4115                 // [HGM] when we will receive the move list we now request, it will be
4116                 // fed to the engine from the first move on. So if the engine is not
4117                 // in the initial position now, bring it there.
4118                 InitChessProgram(&first, 0);
4119             }
4120 #endif
4121             ics_getting_history = H_REQUESTED;
4122             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4123             SendToICS(str);
4124         }
4125         forwardMostMove = backwardMostMove = currentMove = moveNum;
4126     }
4127     
4128     /* Update the clocks */
4129     if (strchr(elapsed_time, '.')) {
4130       /* Time is in ms */
4131       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4132       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4133     } else {
4134       /* Time is in seconds */
4135       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4136       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4137     }
4138       
4139
4140 #if ZIPPY
4141     if (appData.zippyPlay && newGame &&
4142         gameMode != IcsObserving && gameMode != IcsIdle &&
4143         gameMode != IcsExamining)
4144       ZippyFirstBoard(moveNum, basetime, increment);
4145 #endif
4146     
4147     /* Put the move on the move list, first converting
4148        to canonical algebraic form. */
4149     if (moveNum > 0) {
4150   if (appData.debugMode) {
4151     if (appData.debugMode) { int f = forwardMostMove;
4152         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4153                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4154                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4155     }
4156     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4157     fprintf(debugFP, "moveNum = %d\n", moveNum);
4158     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4159     setbuf(debugFP, NULL);
4160   }
4161         if (moveNum <= backwardMostMove) {
4162             /* We don't know what the board looked like before
4163                this move.  Punt. */
4164             strcpy(parseList[moveNum - 1], move_str);
4165             strcat(parseList[moveNum - 1], " ");
4166             strcat(parseList[moveNum - 1], elapsed_time);
4167             moveList[moveNum - 1][0] = NULLCHAR;
4168         } else if (strcmp(move_str, "none") == 0) {
4169             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4170             /* Again, we don't know what the board looked like;
4171                this is really the start of the game. */
4172             parseList[moveNum - 1][0] = NULLCHAR;
4173             moveList[moveNum - 1][0] = NULLCHAR;
4174             backwardMostMove = moveNum;
4175             startedFromSetupPosition = TRUE;
4176             fromX = fromY = toX = toY = -1;
4177         } else {
4178           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4179           //                 So we parse the long-algebraic move string in stead of the SAN move
4180           int valid; char buf[MSG_SIZ], *prom;
4181
4182           // str looks something like "Q/a1-a2"; kill the slash
4183           if(str[1] == '/') 
4184                 sprintf(buf, "%c%s", str[0], str+2);
4185           else  strcpy(buf, str); // might be castling
4186           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4187                 strcat(buf, prom); // long move lacks promo specification!
4188           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4189                 if(appData.debugMode) 
4190                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4191                 strcpy(move_str, buf);
4192           }
4193           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4194                                 &fromX, &fromY, &toX, &toY, &promoChar)
4195                || ParseOneMove(buf, moveNum - 1, &moveType,
4196                                 &fromX, &fromY, &toX, &toY, &promoChar);
4197           // end of long SAN patch
4198           if (valid) {
4199             (void) CoordsToAlgebraic(boards[moveNum - 1],
4200                                      PosFlags(moveNum - 1),
4201                                      fromY, fromX, toY, toX, promoChar,
4202                                      parseList[moveNum-1]);
4203             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4204               case MT_NONE:
4205               case MT_STALEMATE:
4206               default:
4207                 break;
4208               case MT_CHECK:
4209                 if(gameInfo.variant != VariantShogi)
4210                     strcat(parseList[moveNum - 1], "+");
4211                 break;
4212               case MT_CHECKMATE:
4213               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4214                 strcat(parseList[moveNum - 1], "#");
4215                 break;
4216             }
4217             strcat(parseList[moveNum - 1], " ");
4218             strcat(parseList[moveNum - 1], elapsed_time);
4219             /* currentMoveString is set as a side-effect of ParseOneMove */
4220             strcpy(moveList[moveNum - 1], currentMoveString);
4221             strcat(moveList[moveNum - 1], "\n");
4222           } else {
4223             /* Move from ICS was illegal!?  Punt. */
4224   if (appData.debugMode) {
4225     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4226     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4227   }
4228             strcpy(parseList[moveNum - 1], move_str);
4229             strcat(parseList[moveNum - 1], " ");
4230             strcat(parseList[moveNum - 1], elapsed_time);
4231             moveList[moveNum - 1][0] = NULLCHAR;
4232             fromX = fromY = toX = toY = -1;
4233           }
4234         }
4235   if (appData.debugMode) {
4236     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4237     setbuf(debugFP, NULL);
4238   }
4239
4240 #if ZIPPY
4241         /* Send move to chess program (BEFORE animating it). */
4242         if (appData.zippyPlay && !newGame && newMove && 
4243            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4244
4245             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4246                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4247                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4248                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4249                             move_str);
4250                     DisplayError(str, 0);
4251                 } else {
4252                     if (first.sendTime) {
4253                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4254                     }
4255                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4256                     if (firstMove && !bookHit) {
4257                         firstMove = FALSE;
4258                         if (first.useColors) {
4259                           SendToProgram(gameMode == IcsPlayingWhite ?
4260                                         "white\ngo\n" :
4261                                         "black\ngo\n", &first);
4262                         } else {
4263                           SendToProgram("go\n", &first);
4264                         }
4265                         first.maybeThinking = TRUE;
4266                     }
4267                 }
4268             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4269               if (moveList[moveNum - 1][0] == NULLCHAR) {
4270                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4271                 DisplayError(str, 0);
4272               } else {
4273                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4274                 SendMoveToProgram(moveNum - 1, &first);
4275               }
4276             }
4277         }
4278 #endif
4279     }
4280
4281     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4282         /* If move comes from a remote source, animate it.  If it
4283            isn't remote, it will have already been animated. */
4284         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4285             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4286         }
4287         if (!pausing && appData.highlightLastMove) {
4288             SetHighlights(fromX, fromY, toX, toY);
4289         }
4290     }
4291     
4292     /* Start the clocks */
4293     whiteFlag = blackFlag = FALSE;
4294     appData.clockMode = !(basetime == 0 && increment == 0);
4295     if (ticking == 0) {
4296       ics_clock_paused = TRUE;
4297       StopClocks();
4298     } else if (ticking == 1) {
4299       ics_clock_paused = FALSE;
4300     }
4301     if (gameMode == IcsIdle ||
4302         relation == RELATION_OBSERVING_STATIC ||
4303         relation == RELATION_EXAMINING ||
4304         ics_clock_paused)
4305       DisplayBothClocks();
4306     else
4307       StartClocks();
4308     
4309     /* Display opponents and material strengths */
4310     if (gameInfo.variant != VariantBughouse &&
4311         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4312         if (tinyLayout || smallLayout) {
4313             if(gameInfo.variant == VariantNormal)
4314                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4315                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4316                     basetime, increment);
4317             else
4318                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4319                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4320                     basetime, increment, (int) gameInfo.variant);
4321         } else {
4322             if(gameInfo.variant == VariantNormal)
4323                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4324                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4325                     basetime, increment);
4326             else
4327                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4328                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4329                     basetime, increment, VariantName(gameInfo.variant));
4330         }
4331         DisplayTitle(str);
4332   if (appData.debugMode) {
4333     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4334   }
4335     }
4336
4337
4338     /* Display the board */
4339     if (!pausing && !appData.noGUI) {
4340       
4341       if (appData.premove)
4342           if (!gotPremove || 
4343              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4344              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4345               ClearPremoveHighlights();
4346
4347       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4348       DrawPosition(j, boards[currentMove]);
4349
4350       DisplayMove(moveNum - 1);
4351       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4352             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4353               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4354         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4355       }
4356     }
4357
4358     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4359 #if ZIPPY
4360     if(bookHit) { // [HGM] book: simulate book reply
4361         static char bookMove[MSG_SIZ]; // a bit generous?
4362
4363         programStats.nodes = programStats.depth = programStats.time = 
4364         programStats.score = programStats.got_only_move = 0;
4365         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4366
4367         strcpy(bookMove, "move ");
4368         strcat(bookMove, bookHit);
4369         HandleMachineMove(bookMove, &first);
4370     }
4371 #endif
4372 }
4373
4374 void
4375 GetMoveListEvent()
4376 {
4377     char buf[MSG_SIZ];
4378     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4379         ics_getting_history = H_REQUESTED;
4380         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4381         SendToICS(buf);
4382     }
4383 }
4384
4385 void
4386 AnalysisPeriodicEvent(force)
4387      int force;
4388 {
4389     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4390          && !force) || !appData.periodicUpdates)
4391       return;
4392
4393     /* Send . command to Crafty to collect stats */
4394     SendToProgram(".\n", &first);
4395
4396     /* Don't send another until we get a response (this makes
4397        us stop sending to old Crafty's which don't understand
4398        the "." command (sending illegal cmds resets node count & time,
4399        which looks bad)) */
4400     programStats.ok_to_send = 0;
4401 }
4402
4403 void ics_update_width(new_width)
4404         int new_width;
4405 {
4406         ics_printf("set width %d\n", new_width);
4407 }
4408
4409 void
4410 SendMoveToProgram(moveNum, cps)
4411      int moveNum;
4412      ChessProgramState *cps;
4413 {
4414     char buf[MSG_SIZ];
4415
4416     if (cps->useUsermove) {
4417       SendToProgram("usermove ", cps);
4418     }
4419     if (cps->useSAN) {
4420       char *space;
4421       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4422         int len = space - parseList[moveNum];
4423         memcpy(buf, parseList[moveNum], len);
4424         buf[len++] = '\n';
4425         buf[len] = NULLCHAR;
4426       } else {
4427         sprintf(buf, "%s\n", parseList[moveNum]);
4428       }
4429       SendToProgram(buf, cps);
4430     } else {
4431       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4432         AlphaRank(moveList[moveNum], 4);
4433         SendToProgram(moveList[moveNum], cps);
4434         AlphaRank(moveList[moveNum], 4); // and back
4435       } else
4436       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4437        * the engine. It would be nice to have a better way to identify castle 
4438        * moves here. */
4439       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4440                                                                          && cps->useOOCastle) {
4441         int fromX = moveList[moveNum][0] - AAA; 
4442         int fromY = moveList[moveNum][1] - ONE;
4443         int toX = moveList[moveNum][2] - AAA; 
4444         int toY = moveList[moveNum][3] - ONE;
4445         if((boards[moveNum][fromY][fromX] == WhiteKing 
4446             && boards[moveNum][toY][toX] == WhiteRook)
4447            || (boards[moveNum][fromY][fromX] == BlackKing 
4448                && boards[moveNum][toY][toX] == BlackRook)) {
4449           if(toX > fromX) SendToProgram("O-O\n", cps);
4450           else SendToProgram("O-O-O\n", cps);
4451         }
4452         else SendToProgram(moveList[moveNum], cps);
4453       }
4454       else SendToProgram(moveList[moveNum], cps);
4455       /* End of additions by Tord */
4456     }
4457
4458     /* [HGM] setting up the opening has brought engine in force mode! */
4459     /*       Send 'go' if we are in a mode where machine should play. */
4460     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4461         (gameMode == TwoMachinesPlay   ||
4462 #ifdef ZIPPY
4463          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4464 #endif
4465          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4466         SendToProgram("go\n", cps);
4467   if (appData.debugMode) {
4468     fprintf(debugFP, "(extra)\n");
4469   }
4470     }
4471     setboardSpoiledMachineBlack = 0;
4472 }
4473
4474 void
4475 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4476      ChessMove moveType;
4477      int fromX, fromY, toX, toY;
4478 {
4479     char user_move[MSG_SIZ];
4480
4481     switch (moveType) {
4482       default:
4483         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4484                 (int)moveType, fromX, fromY, toX, toY);
4485         DisplayError(user_move + strlen("say "), 0);
4486         break;
4487       case WhiteKingSideCastle:
4488       case BlackKingSideCastle:
4489       case WhiteQueenSideCastleWild:
4490       case BlackQueenSideCastleWild:
4491       /* PUSH Fabien */
4492       case WhiteHSideCastleFR:
4493       case BlackHSideCastleFR:
4494       /* POP Fabien */
4495         sprintf(user_move, "o-o\n");
4496         break;
4497       case WhiteQueenSideCastle:
4498       case BlackQueenSideCastle:
4499       case WhiteKingSideCastleWild:
4500       case BlackKingSideCastleWild:
4501       /* PUSH Fabien */
4502       case WhiteASideCastleFR:
4503       case BlackASideCastleFR:
4504       /* POP Fabien */
4505         sprintf(user_move, "o-o-o\n");
4506         break;
4507       case WhitePromotionQueen:
4508       case BlackPromotionQueen:
4509       case WhitePromotionRook:
4510       case BlackPromotionRook:
4511       case WhitePromotionBishop:
4512       case BlackPromotionBishop:
4513       case WhitePromotionKnight:
4514       case BlackPromotionKnight:
4515       case WhitePromotionKing:
4516       case BlackPromotionKing:
4517       case WhitePromotionChancellor:
4518       case BlackPromotionChancellor:
4519       case WhitePromotionArchbishop:
4520       case BlackPromotionArchbishop:
4521         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4522             sprintf(user_move, "%c%c%c%c=%c\n",
4523                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4524                 PieceToChar(WhiteFerz));
4525         else if(gameInfo.variant == VariantGreat)
4526             sprintf(user_move, "%c%c%c%c=%c\n",
4527                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4528                 PieceToChar(WhiteMan));
4529         else
4530             sprintf(user_move, "%c%c%c%c=%c\n",
4531                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4532                 PieceToChar(PromoPiece(moveType)));
4533         break;
4534       case WhiteDrop:
4535       case BlackDrop:
4536         sprintf(user_move, "%c@%c%c\n",
4537                 ToUpper(PieceToChar((ChessSquare) fromX)),
4538                 AAA + toX, ONE + toY);
4539         break;
4540       case NormalMove:
4541       case WhiteCapturesEnPassant:
4542       case BlackCapturesEnPassant:
4543       case IllegalMove:  /* could be a variant we don't quite understand */
4544         sprintf(user_move, "%c%c%c%c\n",
4545                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4546         break;
4547     }
4548     SendToICS(user_move);
4549     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4550         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4551 }
4552
4553 void
4554 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4555      int rf, ff, rt, ft;
4556      char promoChar;
4557      char move[7];
4558 {
4559     if (rf == DROP_RANK) {
4560         sprintf(move, "%c@%c%c\n",
4561                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4562     } else {
4563         if (promoChar == 'x' || promoChar == NULLCHAR) {
4564             sprintf(move, "%c%c%c%c\n",
4565                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4566         } else {
4567             sprintf(move, "%c%c%c%c%c\n",
4568                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4569         }
4570     }
4571 }
4572
4573 void
4574 ProcessICSInitScript(f)
4575      FILE *f;
4576 {
4577     char buf[MSG_SIZ];
4578
4579     while (fgets(buf, MSG_SIZ, f)) {
4580         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4581     }
4582
4583     fclose(f);
4584 }
4585
4586
4587 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4588 void
4589 AlphaRank(char *move, int n)
4590 {
4591 //    char *p = move, c; int x, y;
4592
4593     if (appData.debugMode) {
4594         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4595     }
4596
4597     if(move[1]=='*' && 
4598        move[2]>='0' && move[2]<='9' &&
4599        move[3]>='a' && move[3]<='x'    ) {
4600         move[1] = '@';
4601         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4602         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4603     } else
4604     if(move[0]>='0' && move[0]<='9' &&
4605        move[1]>='a' && move[1]<='x' &&
4606        move[2]>='0' && move[2]<='9' &&
4607        move[3]>='a' && move[3]<='x'    ) {
4608         /* input move, Shogi -> normal */
4609         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4610         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4611         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4612         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4613     } else
4614     if(move[1]=='@' &&
4615        move[3]>='0' && move[3]<='9' &&
4616        move[2]>='a' && move[2]<='x'    ) {
4617         move[1] = '*';
4618         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4619         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4620     } else
4621     if(
4622        move[0]>='a' && move[0]<='x' &&
4623        move[3]>='0' && move[3]<='9' &&
4624        move[2]>='a' && move[2]<='x'    ) {
4625          /* output move, normal -> Shogi */
4626         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4627         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4628         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4629         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4630         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4631     }
4632     if (appData.debugMode) {
4633         fprintf(debugFP, "   out = '%s'\n", move);
4634     }
4635 }
4636
4637 /* Parser for moves from gnuchess, ICS, or user typein box */
4638 Boolean
4639 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4640      char *move;
4641      int moveNum;
4642      ChessMove *moveType;
4643      int *fromX, *fromY, *toX, *toY;
4644      char *promoChar;
4645 {       
4646     if (appData.debugMode) {
4647         fprintf(debugFP, "move to parse: %s\n", move);
4648     }
4649     *moveType = yylexstr(moveNum, move);
4650
4651     switch (*moveType) {
4652       case WhitePromotionChancellor:
4653       case BlackPromotionChancellor:
4654       case WhitePromotionArchbishop:
4655       case BlackPromotionArchbishop:
4656       case WhitePromotionQueen:
4657       case BlackPromotionQueen:
4658       case WhitePromotionRook:
4659       case BlackPromotionRook:
4660       case WhitePromotionBishop:
4661       case BlackPromotionBishop:
4662       case WhitePromotionKnight:
4663       case BlackPromotionKnight:
4664       case WhitePromotionKing:
4665       case BlackPromotionKing:
4666       case NormalMove:
4667       case WhiteCapturesEnPassant:
4668       case BlackCapturesEnPassant:
4669       case WhiteKingSideCastle:
4670       case WhiteQueenSideCastle:
4671       case BlackKingSideCastle:
4672       case BlackQueenSideCastle:
4673       case WhiteKingSideCastleWild:
4674       case WhiteQueenSideCastleWild:
4675       case BlackKingSideCastleWild:
4676       case BlackQueenSideCastleWild:
4677       /* Code added by Tord: */
4678       case WhiteHSideCastleFR:
4679       case WhiteASideCastleFR:
4680       case BlackHSideCastleFR:
4681       case BlackASideCastleFR:
4682       /* End of code added by Tord */
4683       case IllegalMove:         /* bug or odd chess variant */
4684         *fromX = currentMoveString[0] - AAA;
4685         *fromY = currentMoveString[1] - ONE;
4686         *toX = currentMoveString[2] - AAA;
4687         *toY = currentMoveString[3] - ONE;
4688         *promoChar = currentMoveString[4];
4689         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4690             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4691     if (appData.debugMode) {
4692         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4693     }
4694             *fromX = *fromY = *toX = *toY = 0;
4695             return FALSE;
4696         }
4697         if (appData.testLegality) {
4698           return (*moveType != IllegalMove);
4699         } else {
4700           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4701                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4702         }
4703
4704       case WhiteDrop:
4705       case BlackDrop:
4706         *fromX = *moveType == WhiteDrop ?
4707           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4708           (int) CharToPiece(ToLower(currentMoveString[0]));
4709         *fromY = DROP_RANK;
4710         *toX = currentMoveString[2] - AAA;
4711         *toY = currentMoveString[3] - ONE;
4712         *promoChar = NULLCHAR;
4713         return TRUE;
4714
4715       case AmbiguousMove:
4716       case ImpossibleMove:
4717       case (ChessMove) 0:       /* end of file */
4718       case ElapsedTime:
4719       case Comment:
4720       case PGNTag:
4721       case NAG:
4722       case WhiteWins:
4723       case BlackWins:
4724       case GameIsDrawn:
4725       default:
4726     if (appData.debugMode) {
4727         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4728     }
4729         /* bug? */
4730         *fromX = *fromY = *toX = *toY = 0;
4731         *promoChar = NULLCHAR;
4732         return FALSE;
4733     }
4734 }
4735
4736
4737 void
4738 ParsePV(char *pv)
4739 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4740   int fromX, fromY, toX, toY; char promoChar;
4741   ChessMove moveType;
4742   Boolean valid;
4743   int nr = 0;
4744
4745   endPV = forwardMostMove;
4746   do {
4747     while(*pv == ' ') pv++;
4748     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4749     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4750 if(appData.debugMode){
4751 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4752 }
4753     if(!valid && nr == 0 &&
4754        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4755         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4756     }
4757     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4758     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4759     nr++;
4760     if(endPV+1 > framePtr) break; // no space, truncate
4761     if(!valid) break;
4762     endPV++;
4763     CopyBoard(boards[endPV], boards[endPV-1]);
4764     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4765     moveList[endPV-1][0] = fromX + AAA;
4766     moveList[endPV-1][1] = fromY + ONE;
4767     moveList[endPV-1][2] = toX + AAA;
4768     moveList[endPV-1][3] = toY + ONE;
4769     parseList[endPV-1][0] = NULLCHAR;
4770   } while(valid);
4771   currentMove = endPV;
4772   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4773   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4774                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4775   DrawPosition(TRUE, boards[currentMove]);
4776 }
4777
4778 static int lastX, lastY;
4779
4780 Boolean
4781 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4782 {
4783         int startPV;
4784
4785         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4786         lastX = x; lastY = y;
4787         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4788         startPV = index;
4789       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4790       index = startPV;
4791         while(buf[index] && buf[index] != '\n') index++;
4792         buf[index] = 0;
4793         ParsePV(buf+startPV);
4794         *start = startPV; *end = index-1;
4795         return TRUE;
4796 }
4797
4798 Boolean
4799 LoadPV(int x, int y)
4800 { // called on right mouse click to load PV
4801   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4802   lastX = x; lastY = y;
4803   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4804   return TRUE;
4805 }
4806
4807 void
4808 UnLoadPV()
4809 {
4810   if(endPV < 0) return;
4811   endPV = -1;
4812   currentMove = forwardMostMove;
4813   ClearPremoveHighlights();
4814   DrawPosition(TRUE, boards[currentMove]);
4815 }
4816
4817 void
4818 MovePV(int x, int y, int h)
4819 { // step through PV based on mouse coordinates (called on mouse move)
4820   int margin = h>>3, step = 0;
4821
4822   if(endPV < 0) return;
4823   // we must somehow check if right button is still down (might be released off board!)
4824   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4825   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4826   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4827   if(!step) return;
4828   lastX = x; lastY = y;
4829   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4830   currentMove += step;
4831   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4832   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4833                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4834   DrawPosition(FALSE, boards[currentMove]);
4835 }
4836
4837
4838 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4839 // All positions will have equal probability, but the current method will not provide a unique
4840 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4841 #define DARK 1
4842 #define LITE 2
4843 #define ANY 3
4844
4845 int squaresLeft[4];
4846 int piecesLeft[(int)BlackPawn];
4847 int seed, nrOfShuffles;
4848
4849 void GetPositionNumber()
4850 {       // sets global variable seed
4851         int i;
4852
4853         seed = appData.defaultFrcPosition;
4854         if(seed < 0) { // randomize based on time for negative FRC position numbers
4855                 for(i=0; i<50; i++) seed += random();
4856                 seed = random() ^ random() >> 8 ^ random() << 8;
4857                 if(seed<0) seed = -seed;
4858         }
4859 }
4860
4861 int put(Board board, int pieceType, int rank, int n, int shade)
4862 // put the piece on the (n-1)-th empty squares of the given shade
4863 {
4864         int i;
4865
4866         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4867                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4868                         board[rank][i] = (ChessSquare) pieceType;
4869                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4870                         squaresLeft[ANY]--;
4871                         piecesLeft[pieceType]--; 
4872                         return i;
4873                 }
4874         }
4875         return -1;
4876 }
4877
4878
4879 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4880 // calculate where the next piece goes, (any empty square), and put it there
4881 {
4882         int i;
4883
4884         i = seed % squaresLeft[shade];
4885         nrOfShuffles *= squaresLeft[shade];
4886         seed /= squaresLeft[shade];
4887         put(board, pieceType, rank, i, shade);
4888 }
4889
4890 void AddTwoPieces(Board board, int pieceType, int rank)
4891 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4892 {
4893         int i, n=squaresLeft[ANY], j=n-1, k;
4894
4895         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4896         i = seed % k;  // pick one
4897         nrOfShuffles *= k;
4898         seed /= k;
4899         while(i >= j) i -= j--;
4900         j = n - 1 - j; i += j;
4901         put(board, pieceType, rank, j, ANY);
4902         put(board, pieceType, rank, i, ANY);
4903 }
4904
4905 void SetUpShuffle(Board board, int number)
4906 {
4907         int i, p, first=1;
4908
4909         GetPositionNumber(); nrOfShuffles = 1;
4910
4911         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4912         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4913         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4914
4915         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4916
4917         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4918             p = (int) board[0][i];
4919             if(p < (int) BlackPawn) piecesLeft[p] ++;
4920             board[0][i] = EmptySquare;
4921         }
4922
4923         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4924             // shuffles restricted to allow normal castling put KRR first
4925             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4926                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4927             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4928                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4929             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4930                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4931             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4932                 put(board, WhiteRook, 0, 0, ANY);
4933             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4934         }
4935
4936         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4937             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4938             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4939                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4940                 while(piecesLeft[p] >= 2) {
4941                     AddOnePiece(board, p, 0, LITE);
4942                     AddOnePiece(board, p, 0, DARK);
4943                 }
4944                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4945             }
4946
4947         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4948             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4949             // but we leave King and Rooks for last, to possibly obey FRC restriction
4950             if(p == (int)WhiteRook) continue;
4951             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4952             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4953         }
4954
4955         // now everything is placed, except perhaps King (Unicorn) and Rooks
4956
4957         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4958             // Last King gets castling rights
4959             while(piecesLeft[(int)WhiteUnicorn]) {
4960                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4961                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4962             }
4963
4964             while(piecesLeft[(int)WhiteKing]) {
4965                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4966                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4967             }
4968
4969
4970         } else {
4971             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4972             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4973         }
4974
4975         // Only Rooks can be left; simply place them all
4976         while(piecesLeft[(int)WhiteRook]) {
4977                 i = put(board, WhiteRook, 0, 0, ANY);
4978                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4979                         if(first) {
4980                                 first=0;
4981                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4982                         }
4983                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4984                 }
4985         }
4986         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4987             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4988         }
4989
4990         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4991 }
4992
4993 int SetCharTable( char *table, const char * map )
4994 /* [HGM] moved here from winboard.c because of its general usefulness */
4995 /*       Basically a safe strcpy that uses the last character as King */
4996 {
4997     int result = FALSE; int NrPieces;
4998
4999     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5000                     && NrPieces >= 12 && !(NrPieces&1)) {
5001         int i; /* [HGM] Accept even length from 12 to 34 */
5002
5003         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5004         for( i=0; i<NrPieces/2-1; i++ ) {
5005             table[i] = map[i];
5006             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5007         }
5008         table[(int) WhiteKing]  = map[NrPieces/2-1];
5009         table[(int) BlackKing]  = map[NrPieces-1];
5010
5011         result = TRUE;
5012     }
5013
5014     return result;
5015 }
5016
5017 void Prelude(Board board)
5018 {       // [HGM] superchess: random selection of exo-pieces
5019         int i, j, k; ChessSquare p; 
5020         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5021
5022         GetPositionNumber(); // use FRC position number
5023
5024         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5025             SetCharTable(pieceToChar, appData.pieceToCharTable);
5026             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5027                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5028         }
5029
5030         j = seed%4;                 seed /= 4; 
5031         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5032         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5033         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5034         j = seed%3 + (seed%3 >= j); seed /= 3; 
5035         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5036         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5037         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5038         j = seed%3;                 seed /= 3; 
5039         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5040         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5041         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5042         j = seed%2 + (seed%2 >= j); seed /= 2; 
5043         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5044         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5045         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5046         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5047         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5048         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5049         put(board, exoPieces[0],    0, 0, ANY);
5050         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5051 }
5052
5053 void
5054 InitPosition(redraw)
5055      int redraw;
5056 {
5057     ChessSquare (* pieces)[BOARD_FILES];
5058     int i, j, pawnRow, overrule,
5059     oldx = gameInfo.boardWidth,
5060     oldy = gameInfo.boardHeight,
5061     oldh = gameInfo.holdingsWidth,
5062     oldv = gameInfo.variant;
5063
5064     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5065
5066     /* [AS] Initialize pv info list [HGM] and game status */
5067     {
5068         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5069             pvInfoList[i].depth = 0;
5070             boards[i][EP_STATUS] = EP_NONE;
5071             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5072         }
5073
5074         initialRulePlies = 0; /* 50-move counter start */
5075
5076         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5077         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5078     }
5079
5080     
5081     /* [HGM] logic here is completely changed. In stead of full positions */
5082     /* the initialized data only consist of the two backranks. The switch */
5083     /* selects which one we will use, which is than copied to the Board   */
5084     /* initialPosition, which for the rest is initialized by Pawns and    */
5085     /* empty squares. This initial position is then copied to boards[0],  */
5086     /* possibly after shuffling, so that it remains available.            */
5087
5088     gameInfo.holdingsWidth = 0; /* default board sizes */
5089     gameInfo.boardWidth    = 8;
5090     gameInfo.boardHeight   = 8;
5091     gameInfo.holdingsSize  = 0;
5092     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5093     for(i=0; i<BOARD_FILES-2; i++)
5094       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5095     initialPosition[EP_STATUS] = EP_NONE;
5096     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5097
5098     switch (gameInfo.variant) {
5099     case VariantFischeRandom:
5100       shuffleOpenings = TRUE;
5101     default:
5102       pieces = FIDEArray;
5103       break;
5104     case VariantShatranj:
5105       pieces = ShatranjArray;
5106       nrCastlingRights = 0;
5107       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5108       break;
5109     case VariantMakruk:
5110       pieces = makrukArray;
5111       nrCastlingRights = 0;
5112       startedFromSetupPosition = TRUE;
5113       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5114       break;
5115     case VariantTwoKings:
5116       pieces = twoKingsArray;
5117       break;
5118     case VariantCapaRandom:
5119       shuffleOpenings = TRUE;
5120     case VariantCapablanca:
5121       pieces = CapablancaArray;
5122       gameInfo.boardWidth = 10;
5123       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5124       break;
5125     case VariantGothic:
5126       pieces = GothicArray;
5127       gameInfo.boardWidth = 10;
5128       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5129       break;
5130     case VariantJanus:
5131       pieces = JanusArray;
5132       gameInfo.boardWidth = 10;
5133       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5134       nrCastlingRights = 6;
5135         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5136         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5137         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5138         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5139         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5140         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5141       break;
5142     case VariantFalcon:
5143       pieces = FalconArray;
5144       gameInfo.boardWidth = 10;
5145       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5146       break;
5147     case VariantXiangqi:
5148       pieces = XiangqiArray;
5149       gameInfo.boardWidth  = 9;
5150       gameInfo.boardHeight = 10;
5151       nrCastlingRights = 0;
5152       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5153       break;
5154     case VariantShogi:
5155       pieces = ShogiArray;
5156       gameInfo.boardWidth  = 9;
5157       gameInfo.boardHeight = 9;
5158       gameInfo.holdingsSize = 7;
5159       nrCastlingRights = 0;
5160       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5161       break;
5162     case VariantCourier:
5163       pieces = CourierArray;
5164       gameInfo.boardWidth  = 12;
5165       nrCastlingRights = 0;
5166       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5167       break;
5168     case VariantKnightmate:
5169       pieces = KnightmateArray;
5170       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5171       break;
5172     case VariantFairy:
5173       pieces = fairyArray;
5174       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5175       break;
5176     case VariantGreat:
5177       pieces = GreatArray;
5178       gameInfo.boardWidth = 10;
5179       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5180       gameInfo.holdingsSize = 8;
5181       break;
5182     case VariantSuper:
5183       pieces = FIDEArray;
5184       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5185       gameInfo.holdingsSize = 8;
5186       startedFromSetupPosition = TRUE;
5187       break;
5188     case VariantCrazyhouse:
5189     case VariantBughouse:
5190       pieces = FIDEArray;
5191       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5192       gameInfo.holdingsSize = 5;
5193       break;
5194     case VariantWildCastle:
5195       pieces = FIDEArray;
5196       /* !!?shuffle with kings guaranteed to be on d or e file */
5197       shuffleOpenings = 1;
5198       break;
5199     case VariantNoCastle:
5200       pieces = FIDEArray;
5201       nrCastlingRights = 0;
5202       /* !!?unconstrained back-rank shuffle */
5203       shuffleOpenings = 1;
5204       break;
5205     }
5206
5207     overrule = 0;
5208     if(appData.NrFiles >= 0) {
5209         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5210         gameInfo.boardWidth = appData.NrFiles;
5211     }
5212     if(appData.NrRanks >= 0) {
5213         gameInfo.boardHeight = appData.NrRanks;
5214     }
5215     if(appData.holdingsSize >= 0) {
5216         i = appData.holdingsSize;
5217         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5218         gameInfo.holdingsSize = i;
5219     }
5220     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5221     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5222         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5223
5224     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5225     if(pawnRow < 1) pawnRow = 1;
5226     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5227
5228     /* User pieceToChar list overrules defaults */
5229     if(appData.pieceToCharTable != NULL)
5230         SetCharTable(pieceToChar, appData.pieceToCharTable);
5231
5232     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5233
5234         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5235             s = (ChessSquare) 0; /* account holding counts in guard band */
5236         for( i=0; i<BOARD_HEIGHT; i++ )
5237             initialPosition[i][j] = s;
5238
5239         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5240         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5241         initialPosition[pawnRow][j] = WhitePawn;
5242         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5243         if(gameInfo.variant == VariantXiangqi) {
5244             if(j&1) {
5245                 initialPosition[pawnRow][j] = 
5246                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5247                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5248                    initialPosition[2][j] = WhiteCannon;
5249                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5250                 }
5251             }
5252         }
5253         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5254     }
5255     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5256
5257             j=BOARD_LEFT+1;
5258             initialPosition[1][j] = WhiteBishop;
5259             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5260             j=BOARD_RGHT-2;
5261             initialPosition[1][j] = WhiteRook;
5262             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5263     }
5264
5265     if( nrCastlingRights == -1) {
5266         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5267         /*       This sets default castling rights from none to normal corners   */
5268         /* Variants with other castling rights must set them themselves above    */
5269         nrCastlingRights = 6;
5270        
5271         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5272         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5273         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5274         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5275         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5276         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5277      }
5278
5279      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5280      if(gameInfo.variant == VariantGreat) { // promotion commoners
5281         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5282         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5283         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5284         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5285      }
5286   if (appData.debugMode) {
5287     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5288   }
5289     if(shuffleOpenings) {
5290         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5291         startedFromSetupPosition = TRUE;
5292     }
5293     if(startedFromPositionFile) {
5294       /* [HGM] loadPos: use PositionFile for every new game */
5295       CopyBoard(initialPosition, filePosition);
5296       for(i=0; i<nrCastlingRights; i++)
5297           initialRights[i] = filePosition[CASTLING][i];
5298       startedFromSetupPosition = TRUE;
5299     }
5300
5301     CopyBoard(boards[0], initialPosition);
5302
5303     if(oldx != gameInfo.boardWidth ||
5304        oldy != gameInfo.boardHeight ||
5305        oldh != gameInfo.holdingsWidth
5306 #ifdef GOTHIC
5307        || oldv == VariantGothic ||        // For licensing popups
5308        gameInfo.variant == VariantGothic
5309 #endif
5310 #ifdef FALCON
5311        || oldv == VariantFalcon ||
5312        gameInfo.variant == VariantFalcon
5313 #endif
5314                                          )
5315             InitDrawingSizes(-2 ,0);
5316
5317     if (redraw)
5318       DrawPosition(TRUE, boards[currentMove]);
5319 }
5320
5321 void
5322 SendBoard(cps, moveNum)
5323      ChessProgramState *cps;
5324      int moveNum;
5325 {
5326     char message[MSG_SIZ];
5327     
5328     if (cps->useSetboard) {
5329       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5330       sprintf(message, "setboard %s\n", fen);
5331       SendToProgram(message, cps);
5332       free(fen);
5333
5334     } else {
5335       ChessSquare *bp;
5336       int i, j;
5337       /* Kludge to set black to move, avoiding the troublesome and now
5338        * deprecated "black" command.
5339        */
5340       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5341
5342       SendToProgram("edit\n", cps);
5343       SendToProgram("#\n", cps);
5344       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5345         bp = &boards[moveNum][i][BOARD_LEFT];
5346         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5347           if ((int) *bp < (int) BlackPawn) {
5348             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5349                     AAA + j, ONE + i);
5350             if(message[0] == '+' || message[0] == '~') {
5351                 sprintf(message, "%c%c%c+\n",
5352                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5353                         AAA + j, ONE + i);
5354             }
5355             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5356                 message[1] = BOARD_RGHT   - 1 - j + '1';
5357                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5358             }
5359             SendToProgram(message, cps);
5360           }
5361         }
5362       }
5363     
5364       SendToProgram("c\n", cps);
5365       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5366         bp = &boards[moveNum][i][BOARD_LEFT];
5367         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5368           if (((int) *bp != (int) EmptySquare)
5369               && ((int) *bp >= (int) BlackPawn)) {
5370             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5371                     AAA + j, ONE + i);
5372             if(message[0] == '+' || message[0] == '~') {
5373                 sprintf(message, "%c%c%c+\n",
5374                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5375                         AAA + j, ONE + i);
5376             }
5377             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5378                 message[1] = BOARD_RGHT   - 1 - j + '1';
5379                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5380             }
5381             SendToProgram(message, cps);
5382           }
5383         }
5384       }
5385     
5386       SendToProgram(".\n", cps);
5387     }
5388     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5389 }
5390
5391 static int autoQueen; // [HGM] oneclick
5392
5393 int
5394 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5395 {
5396     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5397     /* [HGM] add Shogi promotions */
5398     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5399     ChessSquare piece;
5400     ChessMove moveType;
5401     Boolean premove;
5402
5403     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5404     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5405
5406     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5407       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5408         return FALSE;
5409
5410     piece = boards[currentMove][fromY][fromX];
5411     if(gameInfo.variant == VariantShogi) {
5412         promotionZoneSize = 3;
5413         highestPromotingPiece = (int)WhiteFerz;
5414     } else if(gameInfo.variant == VariantMakruk) {
5415         promotionZoneSize = 3;
5416     }
5417
5418     // next weed out all moves that do not touch the promotion zone at all
5419     if((int)piece >= BlackPawn) {
5420         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5421              return FALSE;
5422         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5423     } else {
5424         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5425            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5426     }
5427
5428     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5429
5430     // weed out mandatory Shogi promotions
5431     if(gameInfo.variant == VariantShogi) {
5432         if(piece >= BlackPawn) {
5433             if(toY == 0 && piece == BlackPawn ||
5434                toY == 0 && piece == BlackQueen ||
5435                toY <= 1 && piece == BlackKnight) {
5436                 *promoChoice = '+';
5437                 return FALSE;
5438             }
5439         } else {
5440             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5441                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5442                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5443                 *promoChoice = '+';
5444                 return FALSE;
5445             }
5446         }
5447     }
5448
5449     // weed out obviously illegal Pawn moves
5450     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5451         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5452         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5453         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5454         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5455         // note we are not allowed to test for valid (non-)capture, due to premove
5456     }
5457
5458     // we either have a choice what to promote to, or (in Shogi) whether to promote
5459     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5460         *promoChoice = PieceToChar(BlackFerz);  // no choice
5461         return FALSE;
5462     }
5463     if(autoQueen) { // predetermined
5464         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5465              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5466         else *promoChoice = PieceToChar(BlackQueen);
5467         return FALSE;
5468     }
5469
5470     // suppress promotion popup on illegal moves that are not premoves
5471     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5472               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5473     if(appData.testLegality && !premove) {
5474         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5475                         fromY, fromX, toY, toX, NULLCHAR);
5476         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5477            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5478             return FALSE;
5479     }
5480
5481     return TRUE;
5482 }
5483
5484 int
5485 InPalace(row, column)
5486      int row, column;
5487 {   /* [HGM] for Xiangqi */
5488     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5489          column < (BOARD_WIDTH + 4)/2 &&
5490          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5491     return FALSE;
5492 }
5493
5494 int
5495 PieceForSquare (x, y)
5496      int x;
5497      int y;
5498 {
5499   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5500      return -1;
5501   else
5502      return boards[currentMove][y][x];
5503 }
5504
5505 int
5506 OKToStartUserMove(x, y)
5507      int x, y;
5508 {
5509     ChessSquare from_piece;
5510     int white_piece;
5511
5512     if (matchMode) return FALSE;
5513     if (gameMode == EditPosition) return TRUE;
5514
5515     if (x >= 0 && y >= 0)
5516       from_piece = boards[currentMove][y][x];
5517     else
5518       from_piece = EmptySquare;
5519
5520     if (from_piece == EmptySquare) return FALSE;
5521
5522     white_piece = (int)from_piece >= (int)WhitePawn &&
5523       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5524
5525     switch (gameMode) {
5526       case PlayFromGameFile:
5527       case AnalyzeFile:
5528       case TwoMachinesPlay:
5529       case EndOfGame:
5530         return FALSE;
5531
5532       case IcsObserving:
5533       case IcsIdle:
5534         return FALSE;
5535
5536       case MachinePlaysWhite:
5537       case IcsPlayingBlack:
5538         if (appData.zippyPlay) return FALSE;
5539         if (white_piece) {
5540             DisplayMoveError(_("You are playing Black"));
5541             return FALSE;
5542         }
5543         break;
5544
5545       case MachinePlaysBlack:
5546       case IcsPlayingWhite:
5547         if (appData.zippyPlay) return FALSE;
5548         if (!white_piece) {
5549             DisplayMoveError(_("You are playing White"));
5550             return FALSE;
5551         }
5552         break;
5553
5554       case EditGame:
5555         if (!white_piece && WhiteOnMove(currentMove)) {
5556             DisplayMoveError(_("It is White's turn"));
5557             return FALSE;
5558         }           
5559         if (white_piece && !WhiteOnMove(currentMove)) {
5560             DisplayMoveError(_("It is Black's turn"));
5561             return FALSE;
5562         }           
5563         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5564             /* Editing correspondence game history */
5565             /* Could disallow this or prompt for confirmation */
5566             cmailOldMove = -1;
5567         }
5568         break;
5569
5570       case BeginningOfGame:
5571         if (appData.icsActive) return FALSE;
5572         if (!appData.noChessProgram) {
5573             if (!white_piece) {
5574                 DisplayMoveError(_("You are playing White"));
5575                 return FALSE;
5576             }
5577         }
5578         break;
5579         
5580       case Training:
5581         if (!white_piece && WhiteOnMove(currentMove)) {
5582             DisplayMoveError(_("It is White's turn"));
5583             return FALSE;
5584         }           
5585         if (white_piece && !WhiteOnMove(currentMove)) {
5586             DisplayMoveError(_("It is Black's turn"));
5587             return FALSE;
5588         }           
5589         break;
5590
5591       default:
5592       case IcsExamining:
5593         break;
5594     }
5595     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5596         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5597         && gameMode != AnalyzeFile && gameMode != Training) {
5598         DisplayMoveError(_("Displayed position is not current"));
5599         return FALSE;
5600     }
5601     return TRUE;
5602 }
5603
5604 Boolean
5605 OnlyMove(int *x, int *y, Boolean captures) {
5606     DisambiguateClosure cl;
5607     if (appData.zippyPlay) return FALSE;
5608     switch(gameMode) {
5609       case MachinePlaysBlack:
5610       case IcsPlayingWhite:
5611       case BeginningOfGame:
5612         if(!WhiteOnMove(currentMove)) return FALSE;
5613         break;
5614       case MachinePlaysWhite:
5615       case IcsPlayingBlack:
5616         if(WhiteOnMove(currentMove)) return FALSE;
5617         break;
5618       default:
5619         return FALSE;
5620     }
5621     cl.pieceIn = EmptySquare; 
5622     cl.rfIn = *y;
5623     cl.ffIn = *x;
5624     cl.rtIn = -1;
5625     cl.ftIn = -1;
5626     cl.promoCharIn = NULLCHAR;
5627     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5628     if( cl.kind == NormalMove ||
5629         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5630         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5631         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5632         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5633       fromX = cl.ff;
5634       fromY = cl.rf;
5635       *x = cl.ft;
5636       *y = cl.rt;
5637       return TRUE;
5638     }
5639     if(cl.kind != ImpossibleMove) return FALSE;
5640     cl.pieceIn = EmptySquare;
5641     cl.rfIn = -1;
5642     cl.ffIn = -1;
5643     cl.rtIn = *y;
5644     cl.ftIn = *x;
5645     cl.promoCharIn = NULLCHAR;
5646     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5647     if( cl.kind == NormalMove ||
5648         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5649         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5650         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5651         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5652       fromX = cl.ff;
5653       fromY = cl.rf;
5654       *x = cl.ft;
5655       *y = cl.rt;
5656       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5657       return TRUE;
5658     }
5659     return FALSE;
5660 }
5661
5662 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5663 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5664 int lastLoadGameUseList = FALSE;
5665 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5666 ChessMove lastLoadGameStart = (ChessMove) 0;
5667
5668 ChessMove
5669 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5670      int fromX, fromY, toX, toY;
5671      int promoChar;
5672      Boolean captureOwn;
5673 {
5674     ChessMove moveType;
5675     ChessSquare pdown, pup;
5676
5677     /* Check if the user is playing in turn.  This is complicated because we
5678        let the user "pick up" a piece before it is his turn.  So the piece he
5679        tried to pick up may have been captured by the time he puts it down!
5680        Therefore we use the color the user is supposed to be playing in this
5681        test, not the color of the piece that is currently on the starting
5682        square---except in EditGame mode, where the user is playing both
5683        sides; fortunately there the capture race can't happen.  (It can
5684        now happen in IcsExamining mode, but that's just too bad.  The user
5685        will get a somewhat confusing message in that case.)
5686        */
5687
5688     switch (gameMode) {
5689       case PlayFromGameFile:
5690       case AnalyzeFile:
5691       case TwoMachinesPlay:
5692       case EndOfGame:
5693       case IcsObserving:
5694       case IcsIdle:
5695         /* We switched into a game mode where moves are not accepted,
5696            perhaps while the mouse button was down. */
5697         return ImpossibleMove;
5698
5699       case MachinePlaysWhite:
5700         /* User is moving for Black */
5701         if (WhiteOnMove(currentMove)) {
5702             DisplayMoveError(_("It is White's turn"));
5703             return ImpossibleMove;
5704         }
5705         break;
5706
5707       case MachinePlaysBlack:
5708         /* User is moving for White */
5709         if (!WhiteOnMove(currentMove)) {
5710             DisplayMoveError(_("It is Black's turn"));
5711             return ImpossibleMove;
5712         }
5713         break;
5714
5715       case EditGame:
5716       case IcsExamining:
5717       case BeginningOfGame:
5718       case AnalyzeMode:
5719       case Training:
5720         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5721             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5722             /* User is moving for Black */
5723             if (WhiteOnMove(currentMove)) {
5724                 DisplayMoveError(_("It is White's turn"));
5725                 return ImpossibleMove;
5726             }
5727         } else {
5728             /* User is moving for White */
5729             if (!WhiteOnMove(currentMove)) {
5730                 DisplayMoveError(_("It is Black's turn"));
5731                 return ImpossibleMove;
5732             }
5733         }
5734         break;
5735
5736       case IcsPlayingBlack:
5737         /* User is moving for Black */
5738         if (WhiteOnMove(currentMove)) {
5739             if (!appData.premove) {
5740                 DisplayMoveError(_("It is White's turn"));
5741             } else if (toX >= 0 && toY >= 0) {
5742                 premoveToX = toX;
5743                 premoveToY = toY;
5744                 premoveFromX = fromX;
5745                 premoveFromY = fromY;
5746                 premovePromoChar = promoChar;
5747                 gotPremove = 1;
5748                 if (appData.debugMode) 
5749                     fprintf(debugFP, "Got premove: fromX %d,"
5750                             "fromY %d, toX %d, toY %d\n",
5751                             fromX, fromY, toX, toY);
5752             }
5753             return ImpossibleMove;
5754         }
5755         break;
5756
5757       case IcsPlayingWhite:
5758         /* User is moving for White */
5759         if (!WhiteOnMove(currentMove)) {
5760             if (!appData.premove) {
5761                 DisplayMoveError(_("It is Black's turn"));
5762             } else if (toX >= 0 && toY >= 0) {
5763                 premoveToX = toX;
5764                 premoveToY = toY;
5765                 premoveFromX = fromX;
5766                 premoveFromY = fromY;
5767                 premovePromoChar = promoChar;
5768                 gotPremove = 1;
5769                 if (appData.debugMode) 
5770                     fprintf(debugFP, "Got premove: fromX %d,"
5771                             "fromY %d, toX %d, toY %d\n",
5772                             fromX, fromY, toX, toY);
5773             }
5774             return ImpossibleMove;
5775         }
5776         break;
5777
5778       default:
5779         break;
5780
5781       case EditPosition:
5782         /* EditPosition, empty square, or different color piece;
5783            click-click move is possible */
5784         if (toX == -2 || toY == -2) {
5785             boards[0][fromY][fromX] = EmptySquare;
5786             return AmbiguousMove;
5787         } else if (toX >= 0 && toY >= 0) {
5788             boards[0][toY][toX] = boards[0][fromY][fromX];
5789             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5790                 if(boards[0][fromY][0] != EmptySquare) {
5791                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5792                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5793                 }
5794             } else
5795             if(fromX == BOARD_RGHT+1) {
5796                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5797                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5798                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5799                 }
5800             } else
5801             boards[0][fromY][fromX] = EmptySquare;
5802             return AmbiguousMove;
5803         }
5804         return ImpossibleMove;
5805     }
5806
5807     if(toX < 0 || toY < 0) return ImpossibleMove;
5808     pdown = boards[currentMove][fromY][fromX];
5809     pup = boards[currentMove][toY][toX];
5810
5811     /* [HGM] If move started in holdings, it means a drop */
5812     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5813          if( pup != EmptySquare ) return ImpossibleMove;
5814          if(appData.testLegality) {
5815              /* it would be more logical if LegalityTest() also figured out
5816               * which drops are legal. For now we forbid pawns on back rank.
5817               * Shogi is on its own here...
5818               */
5819              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5820                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5821                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5822          }
5823          return WhiteDrop; /* Not needed to specify white or black yet */
5824     }
5825
5826     /* [HGM] always test for legality, to get promotion info */
5827     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5828                                          fromY, fromX, toY, toX, promoChar);
5829     /* [HGM] but possibly ignore an IllegalMove result */
5830     if (appData.testLegality) {
5831         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5832             DisplayMoveError(_("Illegal move"));
5833             return ImpossibleMove;
5834         }
5835     }
5836
5837     return moveType;
5838     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5839        function is made into one that returns an OK move type if FinishMove
5840        should be called. This to give the calling driver routine the
5841        opportunity to finish the userMove input with a promotion popup,
5842        without bothering the user with this for invalid or illegal moves */
5843
5844 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5845 }
5846
5847 /* Common tail of UserMoveEvent and DropMenuEvent */
5848 int
5849 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5850      ChessMove moveType;
5851      int fromX, fromY, toX, toY;
5852      /*char*/int promoChar;
5853 {
5854     char *bookHit = 0;
5855
5856     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5857         // [HGM] superchess: suppress promotions to non-available piece
5858         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5859         if(WhiteOnMove(currentMove)) {
5860             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5861         } else {
5862             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5863         }
5864     }
5865
5866     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5867        move type in caller when we know the move is a legal promotion */
5868     if(moveType == NormalMove && promoChar)
5869         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5870
5871     /* [HGM] convert drag-and-drop piece drops to standard form */
5872     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5873          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5874            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5875                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5876            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5877            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5878            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5879            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5880          fromY = DROP_RANK;
5881     }
5882
5883     /* [HGM] <popupFix> The following if has been moved here from
5884        UserMoveEvent(). Because it seemed to belong here (why not allow
5885        piece drops in training games?), and because it can only be
5886        performed after it is known to what we promote. */
5887     if (gameMode == Training) {
5888       /* compare the move played on the board to the next move in the
5889        * game. If they match, display the move and the opponent's response. 
5890        * If they don't match, display an error message.
5891        */
5892       int saveAnimate;
5893       Board testBoard;
5894       CopyBoard(testBoard, boards[currentMove]);
5895       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5896
5897       if (CompareBoards(testBoard, boards[currentMove+1])) {
5898         ForwardInner(currentMove+1);
5899
5900         /* Autoplay the opponent's response.
5901          * if appData.animate was TRUE when Training mode was entered,
5902          * the response will be animated.
5903          */
5904         saveAnimate = appData.animate;
5905         appData.animate = animateTraining;
5906         ForwardInner(currentMove+1);
5907         appData.animate = saveAnimate;
5908
5909         /* check for the end of the game */
5910         if (currentMove >= forwardMostMove) {
5911           gameMode = PlayFromGameFile;
5912           ModeHighlight();
5913           SetTrainingModeOff();
5914           DisplayInformation(_("End of game"));
5915         }
5916       } else {
5917         DisplayError(_("Incorrect move"), 0);
5918       }
5919       return 1;
5920     }
5921
5922   /* Ok, now we know that the move is good, so we can kill
5923      the previous line in Analysis Mode */
5924   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5925                                 && currentMove < forwardMostMove) {
5926     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5927   }
5928
5929   /* If we need the chess program but it's dead, restart it */
5930   ResurrectChessProgram();
5931
5932   /* A user move restarts a paused game*/
5933   if (pausing)
5934     PauseEvent();
5935
5936   thinkOutput[0] = NULLCHAR;
5937
5938   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5939
5940   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5941
5942   if (gameMode == BeginningOfGame) {
5943     if (appData.noChessProgram) {
5944       gameMode = EditGame;
5945       SetGameInfo();
5946     } else {
5947       char buf[MSG_SIZ];
5948       gameMode = MachinePlaysBlack;
5949       StartClocks();
5950       SetGameInfo();
5951       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5952       DisplayTitle(buf);
5953       if (first.sendName) {
5954         sprintf(buf, "name %s\n", gameInfo.white);
5955         SendToProgram(buf, &first);
5956       }
5957       StartClocks();
5958     }
5959     ModeHighlight();
5960   }
5961
5962   /* Relay move to ICS or chess engine */
5963   if (appData.icsActive) {
5964     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5965         gameMode == IcsExamining) {
5966       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5967         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5968         SendToICS("draw ");
5969         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5970       }
5971       // also send plain move, in case ICS does not understand atomic claims
5972       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5973       ics_user_moved = 1;
5974     }
5975   } else {
5976     if (first.sendTime && (gameMode == BeginningOfGame ||
5977                            gameMode == MachinePlaysWhite ||
5978                            gameMode == MachinePlaysBlack)) {
5979       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5980     }
5981     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5982          // [HGM] book: if program might be playing, let it use book
5983         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5984         first.maybeThinking = TRUE;
5985     } else SendMoveToProgram(forwardMostMove-1, &first);
5986     if (currentMove == cmailOldMove + 1) {
5987       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5988     }
5989   }
5990
5991   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5992
5993   switch (gameMode) {
5994   case EditGame:
5995     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5996     case MT_NONE:
5997     case MT_CHECK:
5998       break;
5999     case MT_CHECKMATE:
6000     case MT_STAINMATE:
6001       if (WhiteOnMove(currentMove)) {
6002         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6003       } else {
6004         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6005       }
6006       break;
6007     case MT_STALEMATE:
6008       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6009       break;
6010     }
6011     break;
6012     
6013   case MachinePlaysBlack:
6014   case MachinePlaysWhite:
6015     /* disable certain menu options while machine is thinking */
6016     SetMachineThinkingEnables();
6017     break;
6018
6019   default:
6020     break;
6021   }
6022
6023   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6024         
6025   if(bookHit) { // [HGM] book: simulate book reply
6026         static char bookMove[MSG_SIZ]; // a bit generous?
6027
6028         programStats.nodes = programStats.depth = programStats.time = 
6029         programStats.score = programStats.got_only_move = 0;
6030         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6031
6032         strcpy(bookMove, "move ");
6033         strcat(bookMove, bookHit);
6034         HandleMachineMove(bookMove, &first);
6035   }
6036   return 1;
6037 }
6038
6039 void
6040 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6041      int fromX, fromY, toX, toY;
6042      int promoChar;
6043 {
6044     /* [HGM] This routine was added to allow calling of its two logical
6045        parts from other modules in the old way. Before, UserMoveEvent()
6046        automatically called FinishMove() if the move was OK, and returned
6047        otherwise. I separated the two, in order to make it possible to
6048        slip a promotion popup in between. But that it always needs two
6049        calls, to the first part, (now called UserMoveTest() ), and to
6050        FinishMove if the first part succeeded. Calls that do not need
6051        to do anything in between, can call this routine the old way. 
6052     */
6053     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6054 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6055     if(moveType == AmbiguousMove)
6056         DrawPosition(FALSE, boards[currentMove]);
6057     else if(moveType != ImpossibleMove && moveType != Comment)
6058         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6059 }
6060
6061 void
6062 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6063      Board board;
6064      int flags;
6065      ChessMove kind;
6066      int rf, ff, rt, ft;
6067      VOIDSTAR closure;
6068 {
6069     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6070     Markers *m = (Markers *) closure;
6071     if(rf == fromY && ff == fromX)
6072         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6073                          || kind == WhiteCapturesEnPassant
6074                          || kind == BlackCapturesEnPassant);
6075     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6076 }
6077
6078 void
6079 MarkTargetSquares(int clear)
6080 {
6081   int x, y;
6082   if(!appData.markers || !appData.highlightDragging || 
6083      !appData.testLegality || gameMode == EditPosition) return;
6084   if(clear) {
6085     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6086   } else {
6087     int capt = 0;
6088     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6089     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6090       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6091       if(capt)
6092       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6093     }
6094   }
6095   DrawPosition(TRUE, NULL);
6096 }
6097
6098 void LeftClick(ClickType clickType, int xPix, int yPix)
6099 {
6100     int x, y;
6101     Boolean saveAnimate;
6102     static int second = 0, promotionChoice = 0;
6103     char promoChoice = NULLCHAR;
6104
6105     if(appData.seekGraph && appData.icsActive && loggedOn &&
6106         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6107         SeekGraphClick(clickType, xPix, yPix, 0);
6108         return;
6109     }
6110
6111     if (clickType == Press) ErrorPopDown();
6112     MarkTargetSquares(1);
6113
6114     x = EventToSquare(xPix, BOARD_WIDTH);
6115     y = EventToSquare(yPix, BOARD_HEIGHT);
6116     if (!flipView && y >= 0) {
6117         y = BOARD_HEIGHT - 1 - y;
6118     }
6119     if (flipView && x >= 0) {
6120         x = BOARD_WIDTH - 1 - x;
6121     }
6122
6123     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6124         if(clickType == Release) return; // ignore upclick of click-click destination
6125         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6126         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6127         if(gameInfo.holdingsWidth && 
6128                 (WhiteOnMove(currentMove) 
6129                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6130                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6131             // click in right holdings, for determining promotion piece
6132             ChessSquare p = boards[currentMove][y][x];
6133             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6134             if(p != EmptySquare) {
6135                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6136                 fromX = fromY = -1;
6137                 return;
6138             }
6139         }
6140         DrawPosition(FALSE, boards[currentMove]);
6141         return;
6142     }
6143
6144     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6145     if(clickType == Press
6146             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6147               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6148               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6149         return;
6150
6151     autoQueen = appData.alwaysPromoteToQueen;
6152
6153     if (fromX == -1) {
6154       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6155         if (clickType == Press) {
6156             /* First square */
6157             if (OKToStartUserMove(x, y)) {
6158                 fromX = x;
6159                 fromY = y;
6160                 second = 0;
6161                 MarkTargetSquares(0);
6162                 DragPieceBegin(xPix, yPix);
6163                 if (appData.highlightDragging) {
6164                     SetHighlights(x, y, -1, -1);
6165                 }
6166             }
6167         }
6168         return;
6169       }
6170     }
6171
6172     /* fromX != -1 */
6173     if (clickType == Press && gameMode != EditPosition) {
6174         ChessSquare fromP;
6175         ChessSquare toP;
6176         int frc;
6177
6178         // ignore off-board to clicks
6179         if(y < 0 || x < 0) return;
6180
6181         /* Check if clicking again on the same color piece */
6182         fromP = boards[currentMove][fromY][fromX];
6183         toP = boards[currentMove][y][x];
6184         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6185         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6186              WhitePawn <= toP && toP <= WhiteKing &&
6187              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6188              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6189             (BlackPawn <= fromP && fromP <= BlackKing && 
6190              BlackPawn <= toP && toP <= BlackKing &&
6191              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6192              !(fromP == BlackKing && toP == BlackRook && frc))) {
6193             /* Clicked again on same color piece -- changed his mind */
6194             second = (x == fromX && y == fromY);
6195            if(!second || !OnlyMove(&x, &y, TRUE)) {
6196             if (appData.highlightDragging) {
6197                 SetHighlights(x, y, -1, -1);
6198             } else {
6199                 ClearHighlights();
6200             }
6201             if (OKToStartUserMove(x, y)) {
6202                 fromX = x;
6203                 fromY = y;
6204                 MarkTargetSquares(0);
6205                 DragPieceBegin(xPix, yPix);
6206             }
6207             return;
6208            }
6209         }
6210         // ignore clicks on holdings
6211         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6212     }
6213
6214     if (clickType == Release && x == fromX && y == fromY) {
6215         DragPieceEnd(xPix, yPix);
6216         if (appData.animateDragging) {
6217             /* Undo animation damage if any */
6218             DrawPosition(FALSE, NULL);
6219         }
6220         if (second) {
6221             /* Second up/down in same square; just abort move */
6222             second = 0;
6223             fromX = fromY = -1;
6224             ClearHighlights();
6225             gotPremove = 0;
6226             ClearPremoveHighlights();
6227         } else {
6228             /* First upclick in same square; start click-click mode */
6229             SetHighlights(x, y, -1, -1);
6230         }
6231         return;
6232     }
6233
6234     /* we now have a different from- and (possibly off-board) to-square */
6235     /* Completed move */
6236     toX = x;
6237     toY = y;
6238     saveAnimate = appData.animate;
6239     if (clickType == Press) {
6240         /* Finish clickclick move */
6241         if (appData.animate || appData.highlightLastMove) {
6242             SetHighlights(fromX, fromY, toX, toY);
6243         } else {
6244             ClearHighlights();
6245         }
6246     } else {
6247         /* Finish drag move */
6248         if (appData.highlightLastMove) {
6249             SetHighlights(fromX, fromY, toX, toY);
6250         } else {
6251             ClearHighlights();
6252         }
6253         DragPieceEnd(xPix, yPix);
6254         /* Don't animate move and drag both */
6255         appData.animate = FALSE;
6256     }
6257
6258     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6259     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6260         ChessSquare piece = boards[currentMove][fromY][fromX];
6261         if(gameMode == EditPosition && piece != EmptySquare &&
6262            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6263             int n;
6264              
6265             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6266                 n = PieceToNumber(piece - (int)BlackPawn);
6267                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6268                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6269                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6270             } else
6271             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6272                 n = PieceToNumber(piece);
6273                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6274                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6275                 boards[currentMove][n][BOARD_WIDTH-2]++;
6276             }
6277             boards[currentMove][fromY][fromX] = EmptySquare;
6278         }
6279         ClearHighlights();
6280         fromX = fromY = -1;
6281         DrawPosition(TRUE, boards[currentMove]);
6282         return;
6283     }
6284
6285     // off-board moves should not be highlighted
6286     if(x < 0 || x < 0) ClearHighlights();
6287
6288     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6289         SetHighlights(fromX, fromY, toX, toY);
6290         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6291             // [HGM] super: promotion to captured piece selected from holdings
6292             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6293             promotionChoice = TRUE;
6294             // kludge follows to temporarily execute move on display, without promoting yet
6295             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6296             boards[currentMove][toY][toX] = p;
6297             DrawPosition(FALSE, boards[currentMove]);
6298             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6299             boards[currentMove][toY][toX] = q;
6300             DisplayMessage("Click in holdings to choose piece", "");
6301             return;
6302         }
6303         PromotionPopUp();
6304     } else {
6305         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6306         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6307         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6308         fromX = fromY = -1;
6309     }
6310     appData.animate = saveAnimate;
6311     if (appData.animate || appData.animateDragging) {
6312         /* Undo animation damage if needed */
6313         DrawPosition(FALSE, NULL);
6314     }
6315 }
6316
6317 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6318 {   // front-end-free part taken out of PieceMenuPopup
6319     int whichMenu; int xSqr, ySqr;
6320
6321     if(seekGraphUp) { // [HGM] seekgraph
6322         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6323         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6324         return -2;
6325     }
6326
6327     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6328          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6329         if(action == Press)   { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
6330         if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
6331         return -2;
6332     }
6333
6334     xSqr = EventToSquare(x, BOARD_WIDTH);
6335     ySqr = EventToSquare(y, BOARD_HEIGHT);
6336     if (action == Release) UnLoadPV(); // [HGM] pv
6337     if (action != Press) return -2; // return code to be ignored
6338     switch (gameMode) {
6339       case IcsExamining:
6340         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6341       case EditPosition:
6342         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6343         if (xSqr < 0 || ySqr < 0) return -1;\r
6344         whichMenu = 0; // edit-position menu
6345         break;
6346       case IcsObserving:
6347         if(!appData.icsEngineAnalyze) return -1;
6348       case IcsPlayingWhite:
6349       case IcsPlayingBlack:
6350         if(!appData.zippyPlay) goto noZip;
6351       case AnalyzeMode:
6352       case AnalyzeFile:
6353       case MachinePlaysWhite:
6354       case MachinePlaysBlack:
6355       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6356         if (!appData.dropMenu) {
6357           LoadPV(x, y);
6358           return 2; // flag front-end to grab mouse events
6359         }
6360         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6361            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6362       case EditGame:
6363       noZip:
6364         if (xSqr < 0 || ySqr < 0) return -1;
6365         if (!appData.dropMenu || appData.testLegality &&
6366             gameInfo.variant != VariantBughouse &&
6367             gameInfo.variant != VariantCrazyhouse) return -1;
6368         whichMenu = 1; // drop menu
6369         break;
6370       default:
6371         return -1;
6372     }
6373
6374     if (((*fromX = xSqr) < 0) ||
6375         ((*fromY = ySqr) < 0)) {
6376         *fromX = *fromY = -1;
6377         return -1;
6378     }
6379     if (flipView)
6380       *fromX = BOARD_WIDTH - 1 - *fromX;
6381     else
6382       *fromY = BOARD_HEIGHT - 1 - *fromY;
6383
6384     return whichMenu;
6385 }
6386
6387 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6388 {
6389 //    char * hint = lastHint;
6390     FrontEndProgramStats stats;
6391
6392     stats.which = cps == &first ? 0 : 1;
6393     stats.depth = cpstats->depth;
6394     stats.nodes = cpstats->nodes;
6395     stats.score = cpstats->score;
6396     stats.time = cpstats->time;
6397     stats.pv = cpstats->movelist;
6398     stats.hint = lastHint;
6399     stats.an_move_index = 0;
6400     stats.an_move_count = 0;
6401
6402     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6403         stats.hint = cpstats->move_name;
6404         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6405         stats.an_move_count = cpstats->nr_moves;
6406     }
6407
6408     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6409
6410     SetProgramStats( &stats );
6411 }
6412
6413 int
6414 Adjudicate(ChessProgramState *cps)
6415 {       // [HGM] some adjudications useful with buggy engines
6416         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6417         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6418         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6419         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6420         int k, count = 0; static int bare = 1;
6421         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6422         Boolean canAdjudicate = !appData.icsActive;
6423
6424         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6425         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6426             if( appData.testLegality )
6427             {   /* [HGM] Some more adjudications for obstinate engines */
6428                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6429                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6430                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6431                 static int moveCount = 6;
6432                 ChessMove result;
6433                 char *reason = NULL;
6434
6435                 /* Count what is on board. */
6436                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6437                 {   ChessSquare p = boards[forwardMostMove][i][j];
6438                     int m=i;
6439
6440                     switch((int) p)
6441                     {   /* count B,N,R and other of each side */
6442                         case WhiteKing:
6443                         case BlackKing:
6444                              NrK++; break; // [HGM] atomic: count Kings
6445                         case WhiteKnight:
6446                              NrWN++; break;
6447                         case WhiteBishop:
6448                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6449                              bishopsColor |= 1 << ((i^j)&1);
6450                              NrWB++; break;
6451                         case BlackKnight:
6452                              NrBN++; break;
6453                         case BlackBishop:
6454                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6455                              bishopsColor |= 1 << ((i^j)&1);
6456                              NrBB++; break;
6457                         case WhiteRook:
6458                              NrWR++; break;
6459                         case BlackRook:
6460                              NrBR++; break;
6461                         case WhiteQueen:
6462                              NrWQ++; break;
6463                         case BlackQueen:
6464                              NrBQ++; break;
6465                         case EmptySquare: 
6466                              break;
6467                         case BlackPawn:
6468                              m = 7-i;
6469                         case WhitePawn:
6470                              PawnAdvance += m; NrPawns++;
6471                     }
6472                     NrPieces += (p != EmptySquare);
6473                     NrW += ((int)p < (int)BlackPawn);
6474                     if(gameInfo.variant == VariantXiangqi && 
6475                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6476                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6477                         NrW -= ((int)p < (int)BlackPawn);
6478                     }
6479                 }
6480
6481                 /* Some material-based adjudications that have to be made before stalemate test */
6482                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6483                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6484                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6485                      if(canAdjudicate && appData.checkMates) {
6486                          if(engineOpponent)
6487                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6488                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6489                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6490                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6491                          return 1;
6492                      }
6493                 }
6494
6495                 /* Bare King in Shatranj (loses) or Losers (wins) */
6496                 if( NrW == 1 || NrPieces - NrW == 1) {
6497                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6498                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6499                      if(canAdjudicate && appData.checkMates) {
6500                          if(engineOpponent)
6501                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6502                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6503                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6504                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6505                          return 1;
6506                      }
6507                   } else
6508                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6509                   {    /* bare King */
6510                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6511                         if(canAdjudicate && appData.checkMates) {
6512                             /* but only adjudicate if adjudication enabled */
6513                             if(engineOpponent)
6514                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6515                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6516                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6517                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6518                             return 1;
6519                         }
6520                   }
6521                 } else bare = 1;
6522
6523
6524             // don't wait for engine to announce game end if we can judge ourselves
6525             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6526               case MT_CHECK:
6527                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6528                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6529                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6530                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6531                             checkCnt++;
6532                         if(checkCnt >= 2) {
6533                             reason = "Xboard adjudication: 3rd check";
6534                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6535                             break;
6536                         }
6537                     }
6538                 }
6539               case MT_NONE:
6540               default:
6541                 break;
6542               case MT_STALEMATE:
6543               case MT_STAINMATE:
6544                 reason = "Xboard adjudication: Stalemate";
6545                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6546                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6547                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6548                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6549                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6550                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6551                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6552                                                                         EP_CHECKMATE : EP_WINS);
6553                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6554                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6555                 }
6556                 break;
6557               case MT_CHECKMATE:
6558                 reason = "Xboard adjudication: Checkmate";
6559                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6560                 break;
6561             }
6562
6563                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6564                     case EP_STALEMATE:
6565                         result = GameIsDrawn; break;
6566                     case EP_CHECKMATE:
6567                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6568                     case EP_WINS:
6569                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6570                     default:
6571                         result = (ChessMove) 0;
6572                 }
6573                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6574                     if(engineOpponent)
6575                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6576                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6577                     GameEnds( result, reason, GE_XBOARD );
6578                     return 1;
6579                 }
6580
6581                 /* Next absolutely insufficient mating material. */
6582                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6583                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6584                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6585                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6586                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6587
6588                      /* always flag draws, for judging claims */
6589                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6590
6591                      if(canAdjudicate && appData.materialDraws) {
6592                          /* but only adjudicate them if adjudication enabled */
6593                          if(engineOpponent) {
6594                            SendToProgram("force\n", engineOpponent); // suppress reply
6595                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6596                          }
6597                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6598                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6599                          return 1;
6600                      }
6601                 }
6602
6603                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6604                 if(NrPieces == 4 && 
6605                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6606                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6607                    || NrWN==2 || NrBN==2     /* KNNK */
6608                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6609                   ) ) {
6610                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6611                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6612                           if(engineOpponent) {
6613                             SendToProgram("force\n", engineOpponent); // suppress reply
6614                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6615                           }
6616                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6617                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6618                           return 1;
6619                      }
6620                 } else moveCount = 6;
6621             }
6622         }
6623           
6624         if (appData.debugMode) { int i;
6625             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6626                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6627                     appData.drawRepeats);
6628             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6629               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6630             
6631         }
6632
6633         // Repetition draws and 50-move rule can be applied independently of legality testing
6634
6635                 /* Check for rep-draws */
6636                 count = 0;
6637                 for(k = forwardMostMove-2;
6638                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6639                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6640                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6641                     k-=2)
6642                 {   int rights=0;
6643                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6644                         /* compare castling rights */
6645                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6646                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6647                                 rights++; /* King lost rights, while rook still had them */
6648                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6649                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6650                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6651                                    rights++; /* but at least one rook lost them */
6652                         }
6653                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6654                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6655                                 rights++; 
6656                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6657                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6658                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6659                                    rights++;
6660                         }
6661                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6662                             && appData.drawRepeats > 1) {
6663                              /* adjudicate after user-specified nr of repeats */
6664                              if(engineOpponent) {
6665                                SendToProgram("force\n", engineOpponent); // suppress reply
6666                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6667                              }
6668                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6669                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6670                                 // [HGM] xiangqi: check for forbidden perpetuals
6671                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6672                                 for(m=forwardMostMove; m>k; m-=2) {
6673                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6674                                         ourPerpetual = 0; // the current mover did not always check
6675                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6676                                         hisPerpetual = 0; // the opponent did not always check
6677                                 }
6678                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6679                                                                         ourPerpetual, hisPerpetual);
6680                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6681                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6682                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6683                                     return 1;
6684                                 }
6685                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6686                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6687                                 // Now check for perpetual chases
6688                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6689                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6690                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6691                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6692                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6693                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6694                                         return 1;
6695                                     }
6696                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6697                                         break; // Abort repetition-checking loop.
6698                                 }
6699                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6700                              }
6701                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6702                              return 1;
6703                         }
6704                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6705                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6706                     }
6707                 }
6708
6709                 /* Now we test for 50-move draws. Determine ply count */
6710                 count = forwardMostMove;
6711                 /* look for last irreversble move */
6712                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6713                     count--;
6714                 /* if we hit starting position, add initial plies */
6715                 if( count == backwardMostMove )
6716                     count -= initialRulePlies;
6717                 count = forwardMostMove - count; 
6718                 if( count >= 100)
6719                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6720                          /* this is used to judge if draw claims are legal */
6721                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6722                          if(engineOpponent) {
6723                            SendToProgram("force\n", engineOpponent); // suppress reply
6724                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6725                          }
6726                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6727                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6728                          return 1;
6729                 }
6730
6731                 /* if draw offer is pending, treat it as a draw claim
6732                  * when draw condition present, to allow engines a way to
6733                  * claim draws before making their move to avoid a race
6734                  * condition occurring after their move
6735                  */
6736                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6737                          char *p = NULL;
6738                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6739                              p = "Draw claim: 50-move rule";
6740                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6741                              p = "Draw claim: 3-fold repetition";
6742                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6743                              p = "Draw claim: insufficient mating material";
6744                          if( p != NULL && canAdjudicate) {
6745                              if(engineOpponent) {
6746                                SendToProgram("force\n", engineOpponent); // suppress reply
6747                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6748                              }
6749                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6750                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6751                              return 1;
6752                          }
6753                 }
6754
6755                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6756                     if(engineOpponent) {
6757                       SendToProgram("force\n", engineOpponent); // suppress reply
6758                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6759                     }
6760                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6761                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6762                     return 1;
6763                 }
6764         return 0;
6765 }
6766
6767 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6768 {   // [HGM] book: this routine intercepts moves to simulate book replies
6769     char *bookHit = NULL;
6770
6771     //first determine if the incoming move brings opponent into his book
6772     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6773         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6774     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6775     if(bookHit != NULL && !cps->bookSuspend) {
6776         // make sure opponent is not going to reply after receiving move to book position
6777         SendToProgram("force\n", cps);
6778         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6779     }
6780     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6781     // now arrange restart after book miss
6782     if(bookHit) {
6783         // after a book hit we never send 'go', and the code after the call to this routine
6784         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6785         char buf[MSG_SIZ];
6786         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6787         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6788         SendToProgram(buf, cps);
6789         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6790     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6791         SendToProgram("go\n", cps);
6792         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6793     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6794         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6795             SendToProgram("go\n", cps); 
6796         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6797     }
6798     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6799 }
6800
6801 char *savedMessage;
6802 ChessProgramState *savedState;
6803 void DeferredBookMove(void)
6804 {
6805         if(savedState->lastPing != savedState->lastPong)
6806                     ScheduleDelayedEvent(DeferredBookMove, 10);
6807         else
6808         HandleMachineMove(savedMessage, savedState);
6809 }
6810
6811 void
6812 HandleMachineMove(message, cps)
6813      char *message;
6814      ChessProgramState *cps;
6815 {
6816     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6817     char realname[MSG_SIZ];
6818     int fromX, fromY, toX, toY;
6819     ChessMove moveType;
6820     char promoChar;
6821     char *p;
6822     int machineWhite;
6823     char *bookHit;
6824
6825     cps->userError = 0;
6826
6827 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6828     /*
6829      * Kludge to ignore BEL characters
6830      */
6831     while (*message == '\007') message++;
6832
6833     /*
6834      * [HGM] engine debug message: ignore lines starting with '#' character
6835      */
6836     if(cps->debug && *message == '#') return;
6837
6838     /*
6839      * Look for book output
6840      */
6841     if (cps == &first && bookRequested) {
6842         if (message[0] == '\t' || message[0] == ' ') {
6843             /* Part of the book output is here; append it */
6844             strcat(bookOutput, message);
6845             strcat(bookOutput, "  \n");
6846             return;
6847         } else if (bookOutput[0] != NULLCHAR) {
6848             /* All of book output has arrived; display it */
6849             char *p = bookOutput;
6850             while (*p != NULLCHAR) {
6851                 if (*p == '\t') *p = ' ';
6852                 p++;
6853             }
6854             DisplayInformation(bookOutput);
6855             bookRequested = FALSE;
6856             /* Fall through to parse the current output */
6857         }
6858     }
6859
6860     /*
6861      * Look for machine move.
6862      */
6863     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6864         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6865     {
6866         /* This method is only useful on engines that support ping */
6867         if (cps->lastPing != cps->lastPong) {
6868           if (gameMode == BeginningOfGame) {
6869             /* Extra move from before last new; ignore */
6870             if (appData.debugMode) {
6871                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6872             }
6873           } else {
6874             if (appData.debugMode) {
6875                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6876                         cps->which, gameMode);
6877             }
6878
6879             SendToProgram("undo\n", cps);
6880           }
6881           return;
6882         }
6883
6884         switch (gameMode) {
6885           case BeginningOfGame:
6886             /* Extra move from before last reset; ignore */
6887             if (appData.debugMode) {
6888                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6889             }
6890             return;
6891
6892           case EndOfGame:
6893           case IcsIdle:
6894           default:
6895             /* Extra move after we tried to stop.  The mode test is
6896                not a reliable way of detecting this problem, but it's
6897                the best we can do on engines that don't support ping.
6898             */
6899             if (appData.debugMode) {
6900                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6901                         cps->which, gameMode);
6902             }
6903             SendToProgram("undo\n", cps);
6904             return;
6905
6906           case MachinePlaysWhite:
6907           case IcsPlayingWhite:
6908             machineWhite = TRUE;
6909             break;
6910
6911           case MachinePlaysBlack:
6912           case IcsPlayingBlack:
6913             machineWhite = FALSE;
6914             break;
6915
6916           case TwoMachinesPlay:
6917             machineWhite = (cps->twoMachinesColor[0] == 'w');
6918             break;
6919         }
6920         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6921             if (appData.debugMode) {
6922                 fprintf(debugFP,
6923                         "Ignoring move out of turn by %s, gameMode %d"
6924                         ", forwardMost %d\n",
6925                         cps->which, gameMode, forwardMostMove);
6926             }
6927             return;
6928         }
6929
6930     if (appData.debugMode) { int f = forwardMostMove;
6931         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6932                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6933                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6934     }
6935         if(cps->alphaRank) AlphaRank(machineMove, 4);
6936         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6937                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6938             /* Machine move could not be parsed; ignore it. */
6939             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6940                     machineMove, cps->which);
6941             DisplayError(buf1, 0);
6942             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6943                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6944             if (gameMode == TwoMachinesPlay) {
6945               GameEnds(machineWhite ? BlackWins : WhiteWins,
6946                        buf1, GE_XBOARD);
6947             }
6948             return;
6949         }
6950
6951         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6952         /* So we have to redo legality test with true e.p. status here,  */
6953         /* to make sure an illegal e.p. capture does not slip through,   */
6954         /* to cause a forfeit on a justified illegal-move complaint      */
6955         /* of the opponent.                                              */
6956         if( gameMode==TwoMachinesPlay && appData.testLegality
6957             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6958                                                               ) {
6959            ChessMove moveType;
6960            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6961                              fromY, fromX, toY, toX, promoChar);
6962             if (appData.debugMode) {
6963                 int i;
6964                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6965                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6966                 fprintf(debugFP, "castling rights\n");
6967             }
6968             if(moveType == IllegalMove) {
6969                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6970                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6971                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6972                            buf1, GE_XBOARD);
6973                 return;
6974            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6975            /* [HGM] Kludge to handle engines that send FRC-style castling
6976               when they shouldn't (like TSCP-Gothic) */
6977            switch(moveType) {
6978              case WhiteASideCastleFR:
6979              case BlackASideCastleFR:
6980                toX+=2;
6981                currentMoveString[2]++;
6982                break;
6983              case WhiteHSideCastleFR:
6984              case BlackHSideCastleFR:
6985                toX--;
6986                currentMoveString[2]--;
6987                break;
6988              default: ; // nothing to do, but suppresses warning of pedantic compilers
6989            }
6990         }
6991         hintRequested = FALSE;
6992         lastHint[0] = NULLCHAR;
6993         bookRequested = FALSE;
6994         /* Program may be pondering now */
6995         cps->maybeThinking = TRUE;
6996         if (cps->sendTime == 2) cps->sendTime = 1;
6997         if (cps->offeredDraw) cps->offeredDraw--;
6998
6999         /* currentMoveString is set as a side-effect of ParseOneMove */
7000         strcpy(machineMove, currentMoveString);
7001         strcat(machineMove, "\n");
7002         strcpy(moveList[forwardMostMove], machineMove);
7003
7004         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7005
7006         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7007         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7008             int count = 0;
7009
7010             while( count < adjudicateLossPlies ) {
7011                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7012
7013                 if( count & 1 ) {
7014                     score = -score; /* Flip score for winning side */
7015                 }
7016
7017                 if( score > adjudicateLossThreshold ) {
7018                     break;
7019                 }
7020
7021                 count++;
7022             }
7023
7024             if( count >= adjudicateLossPlies ) {
7025                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7026
7027                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7028                     "Xboard adjudication", 
7029                     GE_XBOARD );
7030
7031                 return;
7032             }
7033         }
7034
7035         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7036
7037 #if ZIPPY
7038         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7039             first.initDone) {
7040           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7041                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7042                 SendToICS("draw ");
7043                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7044           }
7045           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7046           ics_user_moved = 1;
7047           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7048                 char buf[3*MSG_SIZ];
7049
7050                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7051                         programStats.score / 100.,
7052                         programStats.depth,
7053                         programStats.time / 100.,
7054                         (unsigned int)programStats.nodes,
7055                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7056                         programStats.movelist);
7057                 SendToICS(buf);
7058 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7059           }
7060         }
7061 #endif
7062
7063         /* [AS] Save move info and clear stats for next move */
7064         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7065         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7066         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7067         ClearProgramStats();
7068         thinkOutput[0] = NULLCHAR;
7069         hiddenThinkOutputState = 0;
7070
7071         bookHit = NULL;
7072         if (gameMode == TwoMachinesPlay) {
7073             /* [HGM] relaying draw offers moved to after reception of move */
7074             /* and interpreting offer as claim if it brings draw condition */
7075             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7076                 SendToProgram("draw\n", cps->other);
7077             }
7078             if (cps->other->sendTime) {
7079                 SendTimeRemaining(cps->other,
7080                                   cps->other->twoMachinesColor[0] == 'w');
7081             }
7082             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7083             if (firstMove && !bookHit) {
7084                 firstMove = FALSE;
7085                 if (cps->other->useColors) {
7086                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7087                 }
7088                 SendToProgram("go\n", cps->other);
7089             }
7090             cps->other->maybeThinking = TRUE;
7091         }
7092
7093         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7094         
7095         if (!pausing && appData.ringBellAfterMoves) {
7096             RingBell();
7097         }
7098
7099         /* 
7100          * Reenable menu items that were disabled while
7101          * machine was thinking
7102          */
7103         if (gameMode != TwoMachinesPlay)
7104             SetUserThinkingEnables();
7105
7106         // [HGM] book: after book hit opponent has received move and is now in force mode
7107         // force the book reply into it, and then fake that it outputted this move by jumping
7108         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7109         if(bookHit) {
7110                 static char bookMove[MSG_SIZ]; // a bit generous?
7111
7112                 strcpy(bookMove, "move ");
7113                 strcat(bookMove, bookHit);
7114                 message = bookMove;
7115                 cps = cps->other;
7116                 programStats.nodes = programStats.depth = programStats.time = 
7117                 programStats.score = programStats.got_only_move = 0;
7118                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7119
7120                 if(cps->lastPing != cps->lastPong) {
7121                     savedMessage = message; // args for deferred call
7122                     savedState = cps;
7123                     ScheduleDelayedEvent(DeferredBookMove, 10);
7124                     return;
7125                 }
7126                 goto FakeBookMove;
7127         }
7128
7129         return;
7130     }
7131
7132     /* Set special modes for chess engines.  Later something general
7133      *  could be added here; for now there is just one kludge feature,
7134      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7135      *  when "xboard" is given as an interactive command.
7136      */
7137     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7138         cps->useSigint = FALSE;
7139         cps->useSigterm = FALSE;
7140     }
7141     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7142       ParseFeatures(message+8, cps);
7143       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7144     }
7145
7146     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7147      * want this, I was asked to put it in, and obliged.
7148      */
7149     if (!strncmp(message, "setboard ", 9)) {
7150         Board initial_position;
7151
7152         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7153
7154         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7155             DisplayError(_("Bad FEN received from engine"), 0);
7156             return ;
7157         } else {
7158            Reset(TRUE, FALSE);
7159            CopyBoard(boards[0], initial_position);
7160            initialRulePlies = FENrulePlies;
7161            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7162            else gameMode = MachinePlaysBlack;                 
7163            DrawPosition(FALSE, boards[currentMove]);
7164         }
7165         return;
7166     }
7167
7168     /*
7169      * Look for communication commands
7170      */
7171     if (!strncmp(message, "telluser ", 9)) {
7172         DisplayNote(message + 9);
7173         return;
7174     }
7175     if (!strncmp(message, "tellusererror ", 14)) {
7176         cps->userError = 1;
7177         DisplayError(message + 14, 0);
7178         return;
7179     }
7180     if (!strncmp(message, "tellopponent ", 13)) {
7181       if (appData.icsActive) {
7182         if (loggedOn) {
7183           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7184           SendToICS(buf1);
7185         }
7186       } else {
7187         DisplayNote(message + 13);
7188       }
7189       return;
7190     }
7191     if (!strncmp(message, "tellothers ", 11)) {
7192       if (appData.icsActive) {
7193         if (loggedOn) {
7194           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7195           SendToICS(buf1);
7196         }
7197       }
7198       return;
7199     }
7200     if (!strncmp(message, "tellall ", 8)) {
7201       if (appData.icsActive) {
7202         if (loggedOn) {
7203           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7204           SendToICS(buf1);
7205         }
7206       } else {
7207         DisplayNote(message + 8);
7208       }
7209       return;
7210     }
7211     if (strncmp(message, "warning", 7) == 0) {
7212         /* Undocumented feature, use tellusererror in new code */
7213         DisplayError(message, 0);
7214         return;
7215     }
7216     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7217         strcpy(realname, cps->tidy);
7218         strcat(realname, " query");
7219         AskQuestion(realname, buf2, buf1, cps->pr);
7220         return;
7221     }
7222     /* Commands from the engine directly to ICS.  We don't allow these to be 
7223      *  sent until we are logged on. Crafty kibitzes have been known to 
7224      *  interfere with the login process.
7225      */
7226     if (loggedOn) {
7227         if (!strncmp(message, "tellics ", 8)) {
7228             SendToICS(message + 8);
7229             SendToICS("\n");
7230             return;
7231         }
7232         if (!strncmp(message, "tellicsnoalias ", 15)) {
7233             SendToICS(ics_prefix);
7234             SendToICS(message + 15);
7235             SendToICS("\n");
7236             return;
7237         }
7238         /* The following are for backward compatibility only */
7239         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7240             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7241             SendToICS(ics_prefix);
7242             SendToICS(message);
7243             SendToICS("\n");
7244             return;
7245         }
7246     }
7247     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7248         return;
7249     }
7250     /*
7251      * If the move is illegal, cancel it and redraw the board.
7252      * Also deal with other error cases.  Matching is rather loose
7253      * here to accommodate engines written before the spec.
7254      */
7255     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7256         strncmp(message, "Error", 5) == 0) {
7257         if (StrStr(message, "name") || 
7258             StrStr(message, "rating") || StrStr(message, "?") ||
7259             StrStr(message, "result") || StrStr(message, "board") ||
7260             StrStr(message, "bk") || StrStr(message, "computer") ||
7261             StrStr(message, "variant") || StrStr(message, "hint") ||
7262             StrStr(message, "random") || StrStr(message, "depth") ||
7263             StrStr(message, "accepted")) {
7264             return;
7265         }
7266         if (StrStr(message, "protover")) {
7267           /* Program is responding to input, so it's apparently done
7268              initializing, and this error message indicates it is
7269              protocol version 1.  So we don't need to wait any longer
7270              for it to initialize and send feature commands. */
7271           FeatureDone(cps, 1);
7272           cps->protocolVersion = 1;
7273           return;
7274         }
7275         cps->maybeThinking = FALSE;
7276
7277         if (StrStr(message, "draw")) {
7278             /* Program doesn't have "draw" command */
7279             cps->sendDrawOffers = 0;
7280             return;
7281         }
7282         if (cps->sendTime != 1 &&
7283             (StrStr(message, "time") || StrStr(message, "otim"))) {
7284           /* Program apparently doesn't have "time" or "otim" command */
7285           cps->sendTime = 0;
7286           return;
7287         }
7288         if (StrStr(message, "analyze")) {
7289             cps->analysisSupport = FALSE;
7290             cps->analyzing = FALSE;
7291             Reset(FALSE, TRUE);
7292             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7293             DisplayError(buf2, 0);
7294             return;
7295         }
7296         if (StrStr(message, "(no matching move)st")) {
7297           /* Special kludge for GNU Chess 4 only */
7298           cps->stKludge = TRUE;
7299           SendTimeControl(cps, movesPerSession, timeControl,
7300                           timeIncrement, appData.searchDepth,
7301                           searchTime);
7302           return;
7303         }
7304         if (StrStr(message, "(no matching move)sd")) {
7305           /* Special kludge for GNU Chess 4 only */
7306           cps->sdKludge = TRUE;
7307           SendTimeControl(cps, movesPerSession, timeControl,
7308                           timeIncrement, appData.searchDepth,
7309                           searchTime);
7310           return;
7311         }
7312         if (!StrStr(message, "llegal")) {
7313             return;
7314         }
7315         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7316             gameMode == IcsIdle) return;
7317         if (forwardMostMove <= backwardMostMove) return;
7318         if (pausing) PauseEvent();
7319       if(appData.forceIllegal) {
7320             // [HGM] illegal: machine refused move; force position after move into it
7321           SendToProgram("force\n", cps);
7322           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7323                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7324                 // when black is to move, while there might be nothing on a2 or black
7325                 // might already have the move. So send the board as if white has the move.
7326                 // But first we must change the stm of the engine, as it refused the last move
7327                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7328                 if(WhiteOnMove(forwardMostMove)) {
7329                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7330                     SendBoard(cps, forwardMostMove); // kludgeless board
7331                 } else {
7332                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7333                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7334                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7335                 }
7336           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7337             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7338                  gameMode == TwoMachinesPlay)
7339               SendToProgram("go\n", cps);
7340             return;
7341       } else
7342         if (gameMode == PlayFromGameFile) {
7343             /* Stop reading this game file */
7344             gameMode = EditGame;
7345             ModeHighlight();
7346         }
7347         currentMove = forwardMostMove-1;
7348         DisplayMove(currentMove-1); /* before DisplayMoveError */
7349         SwitchClocks(forwardMostMove-1); // [HGM] race
7350         DisplayBothClocks();
7351         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7352                 parseList[currentMove], cps->which);
7353         DisplayMoveError(buf1);
7354         DrawPosition(FALSE, boards[currentMove]);
7355
7356         /* [HGM] illegal-move claim should forfeit game when Xboard */
7357         /* only passes fully legal moves                            */
7358         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7359             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7360                                 "False illegal-move claim", GE_XBOARD );
7361         }
7362         return;
7363     }
7364     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7365         /* Program has a broken "time" command that
7366            outputs a string not ending in newline.
7367            Don't use it. */
7368         cps->sendTime = 0;
7369     }
7370     
7371     /*
7372      * If chess program startup fails, exit with an error message.
7373      * Attempts to recover here are futile.
7374      */
7375     if ((StrStr(message, "unknown host") != NULL)
7376         || (StrStr(message, "No remote directory") != NULL)
7377         || (StrStr(message, "not found") != NULL)
7378         || (StrStr(message, "No such file") != NULL)
7379         || (StrStr(message, "can't alloc") != NULL)
7380         || (StrStr(message, "Permission denied") != NULL)) {
7381
7382         cps->maybeThinking = FALSE;
7383         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7384                 cps->which, cps->program, cps->host, message);
7385         RemoveInputSource(cps->isr);
7386         DisplayFatalError(buf1, 0, 1);
7387         return;
7388     }
7389     
7390     /* 
7391      * Look for hint output
7392      */
7393     if (sscanf(message, "Hint: %s", buf1) == 1) {
7394         if (cps == &first && hintRequested) {
7395             hintRequested = FALSE;
7396             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7397                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7398                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7399                                     PosFlags(forwardMostMove),
7400                                     fromY, fromX, toY, toX, promoChar, buf1);
7401                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7402                 DisplayInformation(buf2);
7403             } else {
7404                 /* Hint move could not be parsed!? */
7405               snprintf(buf2, sizeof(buf2),
7406                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7407                         buf1, cps->which);
7408                 DisplayError(buf2, 0);
7409             }
7410         } else {
7411             strcpy(lastHint, buf1);
7412         }
7413         return;
7414     }
7415
7416     /*
7417      * Ignore other messages if game is not in progress
7418      */
7419     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7420         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7421
7422     /*
7423      * look for win, lose, draw, or draw offer
7424      */
7425     if (strncmp(message, "1-0", 3) == 0) {
7426         char *p, *q, *r = "";
7427         p = strchr(message, '{');
7428         if (p) {
7429             q = strchr(p, '}');
7430             if (q) {
7431                 *q = NULLCHAR;
7432                 r = p + 1;
7433             }
7434         }
7435         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7436         return;
7437     } else if (strncmp(message, "0-1", 3) == 0) {
7438         char *p, *q, *r = "";
7439         p = strchr(message, '{');
7440         if (p) {
7441             q = strchr(p, '}');
7442             if (q) {
7443                 *q = NULLCHAR;
7444                 r = p + 1;
7445             }
7446         }
7447         /* Kludge for Arasan 4.1 bug */
7448         if (strcmp(r, "Black resigns") == 0) {
7449             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7450             return;
7451         }
7452         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7453         return;
7454     } else if (strncmp(message, "1/2", 3) == 0) {
7455         char *p, *q, *r = "";
7456         p = strchr(message, '{');
7457         if (p) {
7458             q = strchr(p, '}');
7459             if (q) {
7460                 *q = NULLCHAR;
7461                 r = p + 1;
7462             }
7463         }
7464             
7465         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7466         return;
7467
7468     } else if (strncmp(message, "White resign", 12) == 0) {
7469         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7470         return;
7471     } else if (strncmp(message, "Black resign", 12) == 0) {
7472         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7473         return;
7474     } else if (strncmp(message, "White matches", 13) == 0 ||
7475                strncmp(message, "Black matches", 13) == 0   ) {
7476         /* [HGM] ignore GNUShogi noises */
7477         return;
7478     } else if (strncmp(message, "White", 5) == 0 &&
7479                message[5] != '(' &&
7480                StrStr(message, "Black") == NULL) {
7481         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7482         return;
7483     } else if (strncmp(message, "Black", 5) == 0 &&
7484                message[5] != '(') {
7485         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7486         return;
7487     } else if (strcmp(message, "resign") == 0 ||
7488                strcmp(message, "computer resigns") == 0) {
7489         switch (gameMode) {
7490           case MachinePlaysBlack:
7491           case IcsPlayingBlack:
7492             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7493             break;
7494           case MachinePlaysWhite:
7495           case IcsPlayingWhite:
7496             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7497             break;
7498           case TwoMachinesPlay:
7499             if (cps->twoMachinesColor[0] == 'w')
7500               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7501             else
7502               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7503             break;
7504           default:
7505             /* can't happen */
7506             break;
7507         }
7508         return;
7509     } else if (strncmp(message, "opponent mates", 14) == 0) {
7510         switch (gameMode) {
7511           case MachinePlaysBlack:
7512           case IcsPlayingBlack:
7513             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7514             break;
7515           case MachinePlaysWhite:
7516           case IcsPlayingWhite:
7517             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7518             break;
7519           case TwoMachinesPlay:
7520             if (cps->twoMachinesColor[0] == 'w')
7521               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7522             else
7523               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7524             break;
7525           default:
7526             /* can't happen */
7527             break;
7528         }
7529         return;
7530     } else if (strncmp(message, "computer mates", 14) == 0) {
7531         switch (gameMode) {
7532           case MachinePlaysBlack:
7533           case IcsPlayingBlack:
7534             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7535             break;
7536           case MachinePlaysWhite:
7537           case IcsPlayingWhite:
7538             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7539             break;
7540           case TwoMachinesPlay:
7541             if (cps->twoMachinesColor[0] == 'w')
7542               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7543             else
7544               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7545             break;
7546           default:
7547             /* can't happen */
7548             break;
7549         }
7550         return;
7551     } else if (strncmp(message, "checkmate", 9) == 0) {
7552         if (WhiteOnMove(forwardMostMove)) {
7553             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7554         } else {
7555             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7556         }
7557         return;
7558     } else if (strstr(message, "Draw") != NULL ||
7559                strstr(message, "game is a draw") != NULL) {
7560         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7561         return;
7562     } else if (strstr(message, "offer") != NULL &&
7563                strstr(message, "draw") != NULL) {
7564 #if ZIPPY
7565         if (appData.zippyPlay && first.initDone) {
7566             /* Relay offer to ICS */
7567             SendToICS(ics_prefix);
7568             SendToICS("draw\n");
7569         }
7570 #endif
7571         cps->offeredDraw = 2; /* valid until this engine moves twice */
7572         if (gameMode == TwoMachinesPlay) {
7573             if (cps->other->offeredDraw) {
7574                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7575             /* [HGM] in two-machine mode we delay relaying draw offer      */
7576             /* until after we also have move, to see if it is really claim */
7577             }
7578         } else if (gameMode == MachinePlaysWhite ||
7579                    gameMode == MachinePlaysBlack) {
7580           if (userOfferedDraw) {
7581             DisplayInformation(_("Machine accepts your draw offer"));
7582             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7583           } else {
7584             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7585           }
7586         }
7587     }
7588
7589     
7590     /*
7591      * Look for thinking output
7592      */
7593     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7594           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7595                                 ) {
7596         int plylev, mvleft, mvtot, curscore, time;
7597         char mvname[MOVE_LEN];
7598         u64 nodes; // [DM]
7599         char plyext;
7600         int ignore = FALSE;
7601         int prefixHint = FALSE;
7602         mvname[0] = NULLCHAR;
7603
7604         switch (gameMode) {
7605           case MachinePlaysBlack:
7606           case IcsPlayingBlack:
7607             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7608             break;
7609           case MachinePlaysWhite:
7610           case IcsPlayingWhite:
7611             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7612             break;
7613           case AnalyzeMode:
7614           case AnalyzeFile:
7615             break;
7616           case IcsObserving: /* [DM] icsEngineAnalyze */
7617             if (!appData.icsEngineAnalyze) ignore = TRUE;
7618             break;
7619           case TwoMachinesPlay:
7620             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7621                 ignore = TRUE;
7622             }
7623             break;
7624           default:
7625             ignore = TRUE;
7626             break;
7627         }
7628
7629         if (!ignore) {
7630             buf1[0] = NULLCHAR;
7631             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7632                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7633
7634                 if (plyext != ' ' && plyext != '\t') {
7635                     time *= 100;
7636                 }
7637
7638                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7639                 if( cps->scoreIsAbsolute && 
7640                     ( gameMode == MachinePlaysBlack ||
7641                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7642                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7643                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7644                      !WhiteOnMove(currentMove)
7645                     ) )
7646                 {
7647                     curscore = -curscore;
7648                 }
7649
7650
7651                 programStats.depth = plylev;
7652                 programStats.nodes = nodes;
7653                 programStats.time = time;
7654                 programStats.score = curscore;
7655                 programStats.got_only_move = 0;
7656
7657                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7658                         int ticklen;
7659
7660                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7661                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7662                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7663                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7664                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7665                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7666                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7667                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7668                 }
7669
7670                 /* Buffer overflow protection */
7671                 if (buf1[0] != NULLCHAR) {
7672                     if (strlen(buf1) >= sizeof(programStats.movelist)
7673                         && appData.debugMode) {
7674                         fprintf(debugFP,
7675                                 "PV is too long; using the first %u bytes.\n",
7676                                 (unsigned) sizeof(programStats.movelist) - 1);
7677                     }
7678
7679                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7680                 } else {
7681                     sprintf(programStats.movelist, " no PV\n");
7682                 }
7683
7684                 if (programStats.seen_stat) {
7685                     programStats.ok_to_send = 1;
7686                 }
7687
7688                 if (strchr(programStats.movelist, '(') != NULL) {
7689                     programStats.line_is_book = 1;
7690                     programStats.nr_moves = 0;
7691                     programStats.moves_left = 0;
7692                 } else {
7693                     programStats.line_is_book = 0;
7694                 }
7695
7696                 SendProgramStatsToFrontend( cps, &programStats );
7697
7698                 /* 
7699                     [AS] Protect the thinkOutput buffer from overflow... this
7700                     is only useful if buf1 hasn't overflowed first!
7701                 */
7702                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7703                         plylev, 
7704                         (gameMode == TwoMachinesPlay ?
7705                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7706                         ((double) curscore) / 100.0,
7707                         prefixHint ? lastHint : "",
7708                         prefixHint ? " " : "" );
7709
7710                 if( buf1[0] != NULLCHAR ) {
7711                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7712
7713                     if( strlen(buf1) > max_len ) {
7714                         if( appData.debugMode) {
7715                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7716                         }
7717                         buf1[max_len+1] = '\0';
7718                     }
7719
7720                     strcat( thinkOutput, buf1 );
7721                 }
7722
7723                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7724                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7725                     DisplayMove(currentMove - 1);
7726                 }
7727                 return;
7728
7729             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7730                 /* crafty (9.25+) says "(only move) <move>"
7731                  * if there is only 1 legal move
7732                  */
7733                 sscanf(p, "(only move) %s", buf1);
7734                 sprintf(thinkOutput, "%s (only move)", buf1);
7735                 sprintf(programStats.movelist, "%s (only move)", buf1);
7736                 programStats.depth = 1;
7737                 programStats.nr_moves = 1;
7738                 programStats.moves_left = 1;
7739                 programStats.nodes = 1;
7740                 programStats.time = 1;
7741                 programStats.got_only_move = 1;
7742
7743                 /* Not really, but we also use this member to
7744                    mean "line isn't going to change" (Crafty
7745                    isn't searching, so stats won't change) */
7746                 programStats.line_is_book = 1;
7747
7748                 SendProgramStatsToFrontend( cps, &programStats );
7749                 
7750                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7751                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7752                     DisplayMove(currentMove - 1);
7753                 }
7754                 return;
7755             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7756                               &time, &nodes, &plylev, &mvleft,
7757                               &mvtot, mvname) >= 5) {
7758                 /* The stat01: line is from Crafty (9.29+) in response
7759                    to the "." command */
7760                 programStats.seen_stat = 1;
7761                 cps->maybeThinking = TRUE;
7762
7763                 if (programStats.got_only_move || !appData.periodicUpdates)
7764                   return;
7765
7766                 programStats.depth = plylev;
7767                 programStats.time = time;
7768                 programStats.nodes = nodes;
7769                 programStats.moves_left = mvleft;
7770                 programStats.nr_moves = mvtot;
7771                 strcpy(programStats.move_name, mvname);
7772                 programStats.ok_to_send = 1;
7773                 programStats.movelist[0] = '\0';
7774
7775                 SendProgramStatsToFrontend( cps, &programStats );
7776
7777                 return;
7778
7779             } else if (strncmp(message,"++",2) == 0) {
7780                 /* Crafty 9.29+ outputs this */
7781                 programStats.got_fail = 2;
7782                 return;
7783
7784             } else if (strncmp(message,"--",2) == 0) {
7785                 /* Crafty 9.29+ outputs this */
7786                 programStats.got_fail = 1;
7787                 return;
7788
7789             } else if (thinkOutput[0] != NULLCHAR &&
7790                        strncmp(message, "    ", 4) == 0) {
7791                 unsigned message_len;
7792
7793                 p = message;
7794                 while (*p && *p == ' ') p++;
7795
7796                 message_len = strlen( p );
7797
7798                 /* [AS] Avoid buffer overflow */
7799                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7800                     strcat(thinkOutput, " ");
7801                     strcat(thinkOutput, p);
7802                 }
7803
7804                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7805                     strcat(programStats.movelist, " ");
7806                     strcat(programStats.movelist, p);
7807                 }
7808
7809                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7810                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7811                     DisplayMove(currentMove - 1);
7812                 }
7813                 return;
7814             }
7815         }
7816         else {
7817             buf1[0] = NULLCHAR;
7818
7819             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7820                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7821             {
7822                 ChessProgramStats cpstats;
7823
7824                 if (plyext != ' ' && plyext != '\t') {
7825                     time *= 100;
7826                 }
7827
7828                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7829                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7830                     curscore = -curscore;
7831                 }
7832
7833                 cpstats.depth = plylev;
7834                 cpstats.nodes = nodes;
7835                 cpstats.time = time;
7836                 cpstats.score = curscore;
7837                 cpstats.got_only_move = 0;
7838                 cpstats.movelist[0] = '\0';
7839
7840                 if (buf1[0] != NULLCHAR) {
7841                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7842                 }
7843
7844                 cpstats.ok_to_send = 0;
7845                 cpstats.line_is_book = 0;
7846                 cpstats.nr_moves = 0;
7847                 cpstats.moves_left = 0;
7848
7849                 SendProgramStatsToFrontend( cps, &cpstats );
7850             }
7851         }
7852     }
7853 }
7854
7855
7856 /* Parse a game score from the character string "game", and
7857    record it as the history of the current game.  The game
7858    score is NOT assumed to start from the standard position. 
7859    The display is not updated in any way.
7860    */
7861 void
7862 ParseGameHistory(game)
7863      char *game;
7864 {
7865     ChessMove moveType;
7866     int fromX, fromY, toX, toY, boardIndex;
7867     char promoChar;
7868     char *p, *q;
7869     char buf[MSG_SIZ];
7870
7871     if (appData.debugMode)
7872       fprintf(debugFP, "Parsing game history: %s\n", game);
7873
7874     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7875     gameInfo.site = StrSave(appData.icsHost);
7876     gameInfo.date = PGNDate();
7877     gameInfo.round = StrSave("-");
7878
7879     /* Parse out names of players */
7880     while (*game == ' ') game++;
7881     p = buf;
7882     while (*game != ' ') *p++ = *game++;
7883     *p = NULLCHAR;
7884     gameInfo.white = StrSave(buf);
7885     while (*game == ' ') game++;
7886     p = buf;
7887     while (*game != ' ' && *game != '\n') *p++ = *game++;
7888     *p = NULLCHAR;
7889     gameInfo.black = StrSave(buf);
7890
7891     /* Parse moves */
7892     boardIndex = blackPlaysFirst ? 1 : 0;
7893     yynewstr(game);
7894     for (;;) {
7895         yyboardindex = boardIndex;
7896         moveType = (ChessMove) yylex();
7897         switch (moveType) {
7898           case IllegalMove:             /* maybe suicide chess, etc. */
7899   if (appData.debugMode) {
7900     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7901     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7902     setbuf(debugFP, NULL);
7903   }
7904           case WhitePromotionChancellor:
7905           case BlackPromotionChancellor:
7906           case WhitePromotionArchbishop:
7907           case BlackPromotionArchbishop:
7908           case WhitePromotionQueen:
7909           case BlackPromotionQueen:
7910           case WhitePromotionRook:
7911           case BlackPromotionRook:
7912           case WhitePromotionBishop:
7913           case BlackPromotionBishop:
7914           case WhitePromotionKnight:
7915           case BlackPromotionKnight:
7916           case WhitePromotionKing:
7917           case BlackPromotionKing:
7918           case NormalMove:
7919           case WhiteCapturesEnPassant:
7920           case BlackCapturesEnPassant:
7921           case WhiteKingSideCastle:
7922           case WhiteQueenSideCastle:
7923           case BlackKingSideCastle:
7924           case BlackQueenSideCastle:
7925           case WhiteKingSideCastleWild:
7926           case WhiteQueenSideCastleWild:
7927           case BlackKingSideCastleWild:
7928           case BlackQueenSideCastleWild:
7929           /* PUSH Fabien */
7930           case WhiteHSideCastleFR:
7931           case WhiteASideCastleFR:
7932           case BlackHSideCastleFR:
7933           case BlackASideCastleFR:
7934           /* POP Fabien */
7935             fromX = currentMoveString[0] - AAA;
7936             fromY = currentMoveString[1] - ONE;
7937             toX = currentMoveString[2] - AAA;
7938             toY = currentMoveString[3] - ONE;
7939             promoChar = currentMoveString[4];
7940             break;
7941           case WhiteDrop:
7942           case BlackDrop:
7943             fromX = moveType == WhiteDrop ?
7944               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7945             (int) CharToPiece(ToLower(currentMoveString[0]));
7946             fromY = DROP_RANK;
7947             toX = currentMoveString[2] - AAA;
7948             toY = currentMoveString[3] - ONE;
7949             promoChar = NULLCHAR;
7950             break;
7951           case AmbiguousMove:
7952             /* bug? */
7953             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7954   if (appData.debugMode) {
7955     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7956     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7957     setbuf(debugFP, NULL);
7958   }
7959             DisplayError(buf, 0);
7960             return;
7961           case ImpossibleMove:
7962             /* bug? */
7963             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7964   if (appData.debugMode) {
7965     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7966     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7967     setbuf(debugFP, NULL);
7968   }
7969             DisplayError(buf, 0);
7970             return;
7971           case (ChessMove) 0:   /* end of file */
7972             if (boardIndex < backwardMostMove) {
7973                 /* Oops, gap.  How did that happen? */
7974                 DisplayError(_("Gap in move list"), 0);
7975                 return;
7976             }
7977             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7978             if (boardIndex > forwardMostMove) {
7979                 forwardMostMove = boardIndex;
7980             }
7981             return;
7982           case ElapsedTime:
7983             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7984                 strcat(parseList[boardIndex-1], " ");
7985                 strcat(parseList[boardIndex-1], yy_text);
7986             }
7987             continue;
7988           case Comment:
7989           case PGNTag:
7990           case NAG:
7991           default:
7992             /* ignore */
7993             continue;
7994           case WhiteWins:
7995           case BlackWins:
7996           case GameIsDrawn:
7997           case GameUnfinished:
7998             if (gameMode == IcsExamining) {
7999                 if (boardIndex < backwardMostMove) {
8000                     /* Oops, gap.  How did that happen? */
8001                     return;
8002                 }
8003                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8004                 return;
8005             }
8006             gameInfo.result = moveType;
8007             p = strchr(yy_text, '{');
8008             if (p == NULL) p = strchr(yy_text, '(');
8009             if (p == NULL) {
8010                 p = yy_text;
8011                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8012             } else {
8013                 q = strchr(p, *p == '{' ? '}' : ')');
8014                 if (q != NULL) *q = NULLCHAR;
8015                 p++;
8016             }
8017             gameInfo.resultDetails = StrSave(p);
8018             continue;
8019         }
8020         if (boardIndex >= forwardMostMove &&
8021             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8022             backwardMostMove = blackPlaysFirst ? 1 : 0;
8023             return;
8024         }
8025         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8026                                  fromY, fromX, toY, toX, promoChar,
8027                                  parseList[boardIndex]);
8028         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8029         /* currentMoveString is set as a side-effect of yylex */
8030         strcpy(moveList[boardIndex], currentMoveString);
8031         strcat(moveList[boardIndex], "\n");
8032         boardIndex++;
8033         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8034         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8035           case MT_NONE:
8036           case MT_STALEMATE:
8037           default:
8038             break;
8039           case MT_CHECK:
8040             if(gameInfo.variant != VariantShogi)
8041                 strcat(parseList[boardIndex - 1], "+");
8042             break;
8043           case MT_CHECKMATE:
8044           case MT_STAINMATE:
8045             strcat(parseList[boardIndex - 1], "#");
8046             break;
8047         }
8048     }
8049 }
8050
8051
8052 /* Apply a move to the given board  */
8053 void
8054 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8055      int fromX, fromY, toX, toY;
8056      int promoChar;
8057      Board board;
8058 {
8059   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8060   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8061
8062     /* [HGM] compute & store e.p. status and castling rights for new position */
8063     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8064     { int i;
8065
8066       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8067       oldEP = (signed char)board[EP_STATUS];
8068       board[EP_STATUS] = EP_NONE;
8069
8070       if( board[toY][toX] != EmptySquare ) 
8071            board[EP_STATUS] = EP_CAPTURE;  
8072
8073       if( board[fromY][fromX] == WhitePawn ) {
8074            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8075                board[EP_STATUS] = EP_PAWN_MOVE;
8076            if( toY-fromY==2) {
8077                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8078                         gameInfo.variant != VariantBerolina || toX < fromX)
8079                       board[EP_STATUS] = toX | berolina;
8080                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8081                         gameInfo.variant != VariantBerolina || toX > fromX) 
8082                       board[EP_STATUS] = toX;
8083            }
8084       } else 
8085       if( board[fromY][fromX] == BlackPawn ) {
8086            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8087                board[EP_STATUS] = EP_PAWN_MOVE; 
8088            if( toY-fromY== -2) {
8089                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8090                         gameInfo.variant != VariantBerolina || toX < fromX)
8091                       board[EP_STATUS] = toX | berolina;
8092                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8093                         gameInfo.variant != VariantBerolina || toX > fromX) 
8094                       board[EP_STATUS] = toX;
8095            }
8096        }
8097
8098        for(i=0; i<nrCastlingRights; i++) {
8099            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8100               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8101              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8102        }
8103
8104     }
8105
8106   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8107   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8108        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8109          
8110   if (fromX == toX && fromY == toY) return;
8111
8112   if (fromY == DROP_RANK) {
8113         /* must be first */
8114         piece = board[toY][toX] = (ChessSquare) fromX;
8115   } else {
8116      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8117      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8118      if(gameInfo.variant == VariantKnightmate)
8119          king += (int) WhiteUnicorn - (int) WhiteKing;
8120
8121     /* Code added by Tord: */
8122     /* FRC castling assumed when king captures friendly rook. */
8123     if (board[fromY][fromX] == WhiteKing &&
8124              board[toY][toX] == WhiteRook) {
8125       board[fromY][fromX] = EmptySquare;
8126       board[toY][toX] = EmptySquare;
8127       if(toX > fromX) {
8128         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8129       } else {
8130         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8131       }
8132     } else if (board[fromY][fromX] == BlackKing &&
8133                board[toY][toX] == BlackRook) {
8134       board[fromY][fromX] = EmptySquare;
8135       board[toY][toX] = EmptySquare;
8136       if(toX > fromX) {
8137         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8138       } else {
8139         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8140       }
8141     /* End of code added by Tord */
8142
8143     } else if (board[fromY][fromX] == king
8144         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8145         && toY == fromY && toX > fromX+1) {
8146         board[fromY][fromX] = EmptySquare;
8147         board[toY][toX] = king;
8148         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8149         board[fromY][BOARD_RGHT-1] = EmptySquare;
8150     } else if (board[fromY][fromX] == king
8151         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8152                && toY == fromY && toX < fromX-1) {
8153         board[fromY][fromX] = EmptySquare;
8154         board[toY][toX] = king;
8155         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8156         board[fromY][BOARD_LEFT] = EmptySquare;
8157     } else if (board[fromY][fromX] == WhitePawn
8158                && toY >= BOARD_HEIGHT-promoRank
8159                && gameInfo.variant != VariantXiangqi
8160                ) {
8161         /* white pawn promotion */
8162         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8163         if (board[toY][toX] == EmptySquare) {
8164             board[toY][toX] = WhiteQueen;
8165         }
8166         if(gameInfo.variant==VariantBughouse ||
8167            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8168             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8169         board[fromY][fromX] = EmptySquare;
8170     } else if ((fromY == BOARD_HEIGHT-4)
8171                && (toX != fromX)
8172                && gameInfo.variant != VariantXiangqi
8173                && gameInfo.variant != VariantBerolina
8174                && (board[fromY][fromX] == WhitePawn)
8175                && (board[toY][toX] == EmptySquare)) {
8176         board[fromY][fromX] = EmptySquare;
8177         board[toY][toX] = WhitePawn;
8178         captured = board[toY - 1][toX];
8179         board[toY - 1][toX] = EmptySquare;
8180     } else if ((fromY == BOARD_HEIGHT-4)
8181                && (toX == fromX)
8182                && gameInfo.variant == VariantBerolina
8183                && (board[fromY][fromX] == WhitePawn)
8184                && (board[toY][toX] == EmptySquare)) {
8185         board[fromY][fromX] = EmptySquare;
8186         board[toY][toX] = WhitePawn;
8187         if(oldEP & EP_BEROLIN_A) {
8188                 captured = board[fromY][fromX-1];
8189                 board[fromY][fromX-1] = EmptySquare;
8190         }else{  captured = board[fromY][fromX+1];
8191                 board[fromY][fromX+1] = EmptySquare;
8192         }
8193     } else if (board[fromY][fromX] == king
8194         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8195                && toY == fromY && toX > fromX+1) {
8196         board[fromY][fromX] = EmptySquare;
8197         board[toY][toX] = king;
8198         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8199         board[fromY][BOARD_RGHT-1] = EmptySquare;
8200     } else if (board[fromY][fromX] == king
8201         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8202                && toY == fromY && toX < fromX-1) {
8203         board[fromY][fromX] = EmptySquare;
8204         board[toY][toX] = king;
8205         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8206         board[fromY][BOARD_LEFT] = EmptySquare;
8207     } else if (fromY == 7 && fromX == 3
8208                && board[fromY][fromX] == BlackKing
8209                && toY == 7 && toX == 5) {
8210         board[fromY][fromX] = EmptySquare;
8211         board[toY][toX] = BlackKing;
8212         board[fromY][7] = EmptySquare;
8213         board[toY][4] = BlackRook;
8214     } else if (fromY == 7 && fromX == 3
8215                && board[fromY][fromX] == BlackKing
8216                && toY == 7 && toX == 1) {
8217         board[fromY][fromX] = EmptySquare;
8218         board[toY][toX] = BlackKing;
8219         board[fromY][0] = EmptySquare;
8220         board[toY][2] = BlackRook;
8221     } else if (board[fromY][fromX] == BlackPawn
8222                && toY < promoRank
8223                && gameInfo.variant != VariantXiangqi
8224                ) {
8225         /* black pawn promotion */
8226         board[toY][toX] = CharToPiece(ToLower(promoChar));
8227         if (board[toY][toX] == EmptySquare) {
8228             board[toY][toX] = BlackQueen;
8229         }
8230         if(gameInfo.variant==VariantBughouse ||
8231            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8232             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8233         board[fromY][fromX] = EmptySquare;
8234     } else if ((fromY == 3)
8235                && (toX != fromX)
8236                && gameInfo.variant != VariantXiangqi
8237                && gameInfo.variant != VariantBerolina
8238                && (board[fromY][fromX] == BlackPawn)
8239                && (board[toY][toX] == EmptySquare)) {
8240         board[fromY][fromX] = EmptySquare;
8241         board[toY][toX] = BlackPawn;
8242         captured = board[toY + 1][toX];
8243         board[toY + 1][toX] = EmptySquare;
8244     } else if ((fromY == 3)
8245                && (toX == fromX)
8246                && gameInfo.variant == VariantBerolina
8247                && (board[fromY][fromX] == BlackPawn)
8248                && (board[toY][toX] == EmptySquare)) {
8249         board[fromY][fromX] = EmptySquare;
8250         board[toY][toX] = BlackPawn;
8251         if(oldEP & EP_BEROLIN_A) {
8252                 captured = board[fromY][fromX-1];
8253                 board[fromY][fromX-1] = EmptySquare;
8254         }else{  captured = board[fromY][fromX+1];
8255                 board[fromY][fromX+1] = EmptySquare;
8256         }
8257     } else {
8258         board[toY][toX] = board[fromY][fromX];
8259         board[fromY][fromX] = EmptySquare;
8260     }
8261
8262     /* [HGM] now we promote for Shogi, if needed */
8263     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8264         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8265   }
8266
8267     if (gameInfo.holdingsWidth != 0) {
8268
8269       /* !!A lot more code needs to be written to support holdings  */
8270       /* [HGM] OK, so I have written it. Holdings are stored in the */
8271       /* penultimate board files, so they are automaticlly stored   */
8272       /* in the game history.                                       */
8273       if (fromY == DROP_RANK) {
8274         /* Delete from holdings, by decreasing count */
8275         /* and erasing image if necessary            */
8276         p = (int) fromX;
8277         if(p < (int) BlackPawn) { /* white drop */
8278              p -= (int)WhitePawn;
8279                  p = PieceToNumber((ChessSquare)p);
8280              if(p >= gameInfo.holdingsSize) p = 0;
8281              if(--board[p][BOARD_WIDTH-2] <= 0)
8282                   board[p][BOARD_WIDTH-1] = EmptySquare;
8283              if((int)board[p][BOARD_WIDTH-2] < 0)
8284                         board[p][BOARD_WIDTH-2] = 0;
8285         } else {                  /* black drop */
8286              p -= (int)BlackPawn;
8287                  p = PieceToNumber((ChessSquare)p);
8288              if(p >= gameInfo.holdingsSize) p = 0;
8289              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8290                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8291              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8292                         board[BOARD_HEIGHT-1-p][1] = 0;
8293         }
8294       }
8295       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8296           && gameInfo.variant != VariantBughouse        ) {
8297         /* [HGM] holdings: Add to holdings, if holdings exist */
8298         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8299                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8300                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8301         }
8302         p = (int) captured;
8303         if (p >= (int) BlackPawn) {
8304           p -= (int)BlackPawn;
8305           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8306                   /* in Shogi restore piece to its original  first */
8307                   captured = (ChessSquare) (DEMOTED captured);
8308                   p = DEMOTED p;
8309           }
8310           p = PieceToNumber((ChessSquare)p);
8311           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8312           board[p][BOARD_WIDTH-2]++;
8313           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8314         } else {
8315           p -= (int)WhitePawn;
8316           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8317                   captured = (ChessSquare) (DEMOTED captured);
8318                   p = DEMOTED p;
8319           }
8320           p = PieceToNumber((ChessSquare)p);
8321           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8322           board[BOARD_HEIGHT-1-p][1]++;
8323           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8324         }
8325       }
8326     } else if (gameInfo.variant == VariantAtomic) {
8327       if (captured != EmptySquare) {
8328         int y, x;
8329         for (y = toY-1; y <= toY+1; y++) {
8330           for (x = toX-1; x <= toX+1; x++) {
8331             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8332                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8333               board[y][x] = EmptySquare;
8334             }
8335           }
8336         }
8337         board[toY][toX] = EmptySquare;
8338       }
8339     }
8340     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8341         /* [HGM] Shogi promotions */
8342         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8343     }
8344
8345     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8346                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8347         // [HGM] superchess: take promotion piece out of holdings
8348         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8349         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8350             if(!--board[k][BOARD_WIDTH-2])
8351                 board[k][BOARD_WIDTH-1] = EmptySquare;
8352         } else {
8353             if(!--board[BOARD_HEIGHT-1-k][1])
8354                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8355         }
8356     }
8357
8358 }
8359
8360 /* Updates forwardMostMove */
8361 void
8362 MakeMove(fromX, fromY, toX, toY, promoChar)
8363      int fromX, fromY, toX, toY;
8364      int promoChar;
8365 {
8366 //    forwardMostMove++; // [HGM] bare: moved downstream
8367
8368     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8369         int timeLeft; static int lastLoadFlag=0; int king, piece;
8370         piece = boards[forwardMostMove][fromY][fromX];
8371         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8372         if(gameInfo.variant == VariantKnightmate)
8373             king += (int) WhiteUnicorn - (int) WhiteKing;
8374         if(forwardMostMove == 0) {
8375             if(blackPlaysFirst) 
8376                 fprintf(serverMoves, "%s;", second.tidy);
8377             fprintf(serverMoves, "%s;", first.tidy);
8378             if(!blackPlaysFirst) 
8379                 fprintf(serverMoves, "%s;", second.tidy);
8380         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8381         lastLoadFlag = loadFlag;
8382         // print base move
8383         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8384         // print castling suffix
8385         if( toY == fromY && piece == king ) {
8386             if(toX-fromX > 1)
8387                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8388             if(fromX-toX >1)
8389                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8390         }
8391         // e.p. suffix
8392         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8393              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8394              boards[forwardMostMove][toY][toX] == EmptySquare
8395              && fromX != toX )
8396                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8397         // promotion suffix
8398         if(promoChar != NULLCHAR)
8399                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8400         if(!loadFlag) {
8401             fprintf(serverMoves, "/%d/%d",
8402                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8403             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8404             else                      timeLeft = blackTimeRemaining/1000;
8405             fprintf(serverMoves, "/%d", timeLeft);
8406         }
8407         fflush(serverMoves);
8408     }
8409
8410     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8411       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8412                         0, 1);
8413       return;
8414     }
8415     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8416     if (commentList[forwardMostMove+1] != NULL) {
8417         free(commentList[forwardMostMove+1]);
8418         commentList[forwardMostMove+1] = NULL;
8419     }
8420     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8421     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8422     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8423     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8424     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8425     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8426     gameInfo.result = GameUnfinished;
8427     if (gameInfo.resultDetails != NULL) {
8428         free(gameInfo.resultDetails);
8429         gameInfo.resultDetails = NULL;
8430     }
8431     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8432                               moveList[forwardMostMove - 1]);
8433     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8434                              PosFlags(forwardMostMove - 1),
8435                              fromY, fromX, toY, toX, promoChar,
8436                              parseList[forwardMostMove - 1]);
8437     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8438       case MT_NONE:
8439       case MT_STALEMATE:
8440       default:
8441         break;
8442       case MT_CHECK:
8443         if(gameInfo.variant != VariantShogi)
8444             strcat(parseList[forwardMostMove - 1], "+");
8445         break;
8446       case MT_CHECKMATE:
8447       case MT_STAINMATE:
8448         strcat(parseList[forwardMostMove - 1], "#");
8449         break;
8450     }
8451     if (appData.debugMode) {
8452         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8453     }
8454
8455 }
8456
8457 /* Updates currentMove if not pausing */
8458 void
8459 ShowMove(fromX, fromY, toX, toY)
8460 {
8461     int instant = (gameMode == PlayFromGameFile) ?
8462         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8463     if(appData.noGUI) return;
8464     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8465         if (!instant) {
8466             if (forwardMostMove == currentMove + 1) {
8467                 AnimateMove(boards[forwardMostMove - 1],
8468                             fromX, fromY, toX, toY);
8469             }
8470             if (appData.highlightLastMove) {
8471                 SetHighlights(fromX, fromY, toX, toY);
8472             }
8473         }
8474         currentMove = forwardMostMove;
8475     }
8476
8477     if (instant) return;
8478
8479     DisplayMove(currentMove - 1);
8480     DrawPosition(FALSE, boards[currentMove]);
8481     DisplayBothClocks();
8482     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8483 }
8484
8485 void SendEgtPath(ChessProgramState *cps)
8486 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8487         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8488
8489         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8490
8491         while(*p) {
8492             char c, *q = name+1, *r, *s;
8493
8494             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8495             while(*p && *p != ',') *q++ = *p++;
8496             *q++ = ':'; *q = 0;
8497             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8498                 strcmp(name, ",nalimov:") == 0 ) {
8499                 // take nalimov path from the menu-changeable option first, if it is defined
8500                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8501                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8502             } else
8503             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8504                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8505                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8506                 s = r = StrStr(s, ":") + 1; // beginning of path info
8507                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8508                 c = *r; *r = 0;             // temporarily null-terminate path info
8509                     *--q = 0;               // strip of trailig ':' from name
8510                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8511                 *r = c;
8512                 SendToProgram(buf,cps);     // send egtbpath command for this format
8513             }
8514             if(*p == ',') p++; // read away comma to position for next format name
8515         }
8516 }
8517
8518 void
8519 InitChessProgram(cps, setup)
8520      ChessProgramState *cps;
8521      int setup; /* [HGM] needed to setup FRC opening position */
8522 {
8523     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8524     if (appData.noChessProgram) return;
8525     hintRequested = FALSE;
8526     bookRequested = FALSE;
8527
8528     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8529     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8530     if(cps->memSize) { /* [HGM] memory */
8531         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8532         SendToProgram(buf, cps);
8533     }
8534     SendEgtPath(cps); /* [HGM] EGT */
8535     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8536         sprintf(buf, "cores %d\n", appData.smpCores);
8537         SendToProgram(buf, cps);
8538     }
8539
8540     SendToProgram(cps->initString, cps);
8541     if (gameInfo.variant != VariantNormal &&
8542         gameInfo.variant != VariantLoadable
8543         /* [HGM] also send variant if board size non-standard */
8544         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8545                                             ) {
8546       char *v = VariantName(gameInfo.variant);
8547       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8548         /* [HGM] in protocol 1 we have to assume all variants valid */
8549         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8550         DisplayFatalError(buf, 0, 1);
8551         return;
8552       }
8553
8554       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8555       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8556       if( gameInfo.variant == VariantXiangqi )
8557            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8558       if( gameInfo.variant == VariantShogi )
8559            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8560       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8561            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8562       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8563                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8564            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8565       if( gameInfo.variant == VariantCourier )
8566            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8567       if( gameInfo.variant == VariantSuper )
8568            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8569       if( gameInfo.variant == VariantGreat )
8570            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8571
8572       if(overruled) {
8573            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8574                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8575            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8576            if(StrStr(cps->variants, b) == NULL) { 
8577                // specific sized variant not known, check if general sizing allowed
8578                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8579                    if(StrStr(cps->variants, "boardsize") == NULL) {
8580                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8581                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8582                        DisplayFatalError(buf, 0, 1);
8583                        return;
8584                    }
8585                    /* [HGM] here we really should compare with the maximum supported board size */
8586                }
8587            }
8588       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8589       sprintf(buf, "variant %s\n", b);
8590       SendToProgram(buf, cps);
8591     }
8592     currentlyInitializedVariant = gameInfo.variant;
8593
8594     /* [HGM] send opening position in FRC to first engine */
8595     if(setup) {
8596           SendToProgram("force\n", cps);
8597           SendBoard(cps, 0);
8598           /* engine is now in force mode! Set flag to wake it up after first move. */
8599           setboardSpoiledMachineBlack = 1;
8600     }
8601
8602     if (cps->sendICS) {
8603       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8604       SendToProgram(buf, cps);
8605     }
8606     cps->maybeThinking = FALSE;
8607     cps->offeredDraw = 0;
8608     if (!appData.icsActive) {
8609         SendTimeControl(cps, movesPerSession, timeControl,
8610                         timeIncrement, appData.searchDepth,
8611                         searchTime);
8612     }
8613     if (appData.showThinking 
8614         // [HGM] thinking: four options require thinking output to be sent
8615         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8616                                 ) {
8617         SendToProgram("post\n", cps);
8618     }
8619     SendToProgram("hard\n", cps);
8620     if (!appData.ponderNextMove) {
8621         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8622            it without being sure what state we are in first.  "hard"
8623            is not a toggle, so that one is OK.
8624          */
8625         SendToProgram("easy\n", cps);
8626     }
8627     if (cps->usePing) {
8628       sprintf(buf, "ping %d\n", ++cps->lastPing);
8629       SendToProgram(buf, cps);
8630     }
8631     cps->initDone = TRUE;
8632 }   
8633
8634
8635 void
8636 StartChessProgram(cps)
8637      ChessProgramState *cps;
8638 {
8639     char buf[MSG_SIZ];
8640     int err;
8641
8642     if (appData.noChessProgram) return;
8643     cps->initDone = FALSE;
8644
8645     if (strcmp(cps->host, "localhost") == 0) {
8646         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8647     } else if (*appData.remoteShell == NULLCHAR) {
8648         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8649     } else {
8650         if (*appData.remoteUser == NULLCHAR) {
8651           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8652                     cps->program);
8653         } else {
8654           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8655                     cps->host, appData.remoteUser, cps->program);
8656         }
8657         err = StartChildProcess(buf, "", &cps->pr);
8658     }
8659     
8660     if (err != 0) {
8661         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8662         DisplayFatalError(buf, err, 1);
8663         cps->pr = NoProc;
8664         cps->isr = NULL;
8665         return;
8666     }
8667     
8668     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8669     if (cps->protocolVersion > 1) {
8670       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8671       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8672       cps->comboCnt = 0;  //                and values of combo boxes
8673       SendToProgram(buf, cps);
8674     } else {
8675       SendToProgram("xboard\n", cps);
8676     }
8677 }
8678
8679
8680 void
8681 TwoMachinesEventIfReady P((void))
8682 {
8683   if (first.lastPing != first.lastPong) {
8684     DisplayMessage("", _("Waiting for first chess program"));
8685     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8686     return;
8687   }
8688   if (second.lastPing != second.lastPong) {
8689     DisplayMessage("", _("Waiting for second chess program"));
8690     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8691     return;
8692   }
8693   ThawUI();
8694   TwoMachinesEvent();
8695 }
8696
8697 void
8698 NextMatchGame P((void))
8699 {
8700     int index; /* [HGM] autoinc: step load index during match */
8701     Reset(FALSE, TRUE);
8702     if (*appData.loadGameFile != NULLCHAR) {
8703         index = appData.loadGameIndex;
8704         if(index < 0) { // [HGM] autoinc
8705             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8706             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8707         } 
8708         LoadGameFromFile(appData.loadGameFile,
8709                          index,
8710                          appData.loadGameFile, FALSE);
8711     } else if (*appData.loadPositionFile != NULLCHAR) {
8712         index = appData.loadPositionIndex;
8713         if(index < 0) { // [HGM] autoinc
8714             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8715             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8716         } 
8717         LoadPositionFromFile(appData.loadPositionFile,
8718                              index,
8719                              appData.loadPositionFile);
8720     }
8721     TwoMachinesEventIfReady();
8722 }
8723
8724 void UserAdjudicationEvent( int result )
8725 {
8726     ChessMove gameResult = GameIsDrawn;
8727
8728     if( result > 0 ) {
8729         gameResult = WhiteWins;
8730     }
8731     else if( result < 0 ) {
8732         gameResult = BlackWins;
8733     }
8734
8735     if( gameMode == TwoMachinesPlay ) {
8736         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8737     }
8738 }
8739
8740
8741 // [HGM] save: calculate checksum of game to make games easily identifiable
8742 int StringCheckSum(char *s)
8743 {
8744         int i = 0;
8745         if(s==NULL) return 0;
8746         while(*s) i = i*259 + *s++;
8747         return i;
8748 }
8749
8750 int GameCheckSum()
8751 {
8752         int i, sum=0;
8753         for(i=backwardMostMove; i<forwardMostMove; i++) {
8754                 sum += pvInfoList[i].depth;
8755                 sum += StringCheckSum(parseList[i]);
8756                 sum += StringCheckSum(commentList[i]);
8757                 sum *= 261;
8758         }
8759         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8760         return sum + StringCheckSum(commentList[i]);
8761 } // end of save patch
8762
8763 void
8764 GameEnds(result, resultDetails, whosays)
8765      ChessMove result;
8766      char *resultDetails;
8767      int whosays;
8768 {
8769     GameMode nextGameMode;
8770     int isIcsGame;
8771     char buf[MSG_SIZ];
8772
8773     if(endingGame) return; /* [HGM] crash: forbid recursion */
8774     endingGame = 1;
8775
8776     if (appData.debugMode) {
8777       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8778               result, resultDetails ? resultDetails : "(null)", whosays);
8779     }
8780
8781     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8782
8783     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8784         /* If we are playing on ICS, the server decides when the
8785            game is over, but the engine can offer to draw, claim 
8786            a draw, or resign. 
8787          */
8788 #if ZIPPY
8789         if (appData.zippyPlay && first.initDone) {
8790             if (result == GameIsDrawn) {
8791                 /* In case draw still needs to be claimed */
8792                 SendToICS(ics_prefix);
8793                 SendToICS("draw\n");
8794             } else if (StrCaseStr(resultDetails, "resign")) {
8795                 SendToICS(ics_prefix);
8796                 SendToICS("resign\n");
8797             }
8798         }
8799 #endif
8800         endingGame = 0; /* [HGM] crash */
8801         return;
8802     }
8803
8804     /* If we're loading the game from a file, stop */
8805     if (whosays == GE_FILE) {
8806       (void) StopLoadGameTimer();
8807       gameFileFP = NULL;
8808     }
8809
8810     /* Cancel draw offers */
8811     first.offeredDraw = second.offeredDraw = 0;
8812
8813     /* If this is an ICS game, only ICS can really say it's done;
8814        if not, anyone can. */
8815     isIcsGame = (gameMode == IcsPlayingWhite || 
8816                  gameMode == IcsPlayingBlack || 
8817                  gameMode == IcsObserving    || 
8818                  gameMode == IcsExamining);
8819
8820     if (!isIcsGame || whosays == GE_ICS) {
8821         /* OK -- not an ICS game, or ICS said it was done */
8822         StopClocks();
8823         if (!isIcsGame && !appData.noChessProgram) 
8824           SetUserThinkingEnables();
8825     
8826         /* [HGM] if a machine claims the game end we verify this claim */
8827         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8828             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8829                 char claimer;
8830                 ChessMove trueResult = (ChessMove) -1;
8831
8832                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8833                                             first.twoMachinesColor[0] :
8834                                             second.twoMachinesColor[0] ;
8835
8836                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8837                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8838                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8839                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8840                 } else
8841                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8842                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8843                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8844                 } else
8845                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8846                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8847                 }
8848
8849                 // now verify win claims, but not in drop games, as we don't understand those yet
8850                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8851                                                  || gameInfo.variant == VariantGreat) &&
8852                     (result == WhiteWins && claimer == 'w' ||
8853                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8854                       if (appData.debugMode) {
8855                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8856                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8857                       }
8858                       if(result != trueResult) {
8859                               sprintf(buf, "False win claim: '%s'", resultDetails);
8860                               result = claimer == 'w' ? BlackWins : WhiteWins;
8861                               resultDetails = buf;
8862                       }
8863                 } else
8864                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8865                     && (forwardMostMove <= backwardMostMove ||
8866                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8867                         (claimer=='b')==(forwardMostMove&1))
8868                                                                                   ) {
8869                       /* [HGM] verify: draws that were not flagged are false claims */
8870                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8871                       result = claimer == 'w' ? BlackWins : WhiteWins;
8872                       resultDetails = buf;
8873                 }
8874                 /* (Claiming a loss is accepted no questions asked!) */
8875             }
8876             /* [HGM] bare: don't allow bare King to win */
8877             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8878                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8879                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8880                && result != GameIsDrawn)
8881             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8882                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8883                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8884                         if(p >= 0 && p <= (int)WhiteKing) k++;
8885                 }
8886                 if (appData.debugMode) {
8887                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8888                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8889                 }
8890                 if(k <= 1) {
8891                         result = GameIsDrawn;
8892                         sprintf(buf, "%s but bare king", resultDetails);
8893                         resultDetails = buf;
8894                 }
8895             }
8896         }
8897
8898
8899         if(serverMoves != NULL && !loadFlag) { char c = '=';
8900             if(result==WhiteWins) c = '+';
8901             if(result==BlackWins) c = '-';
8902             if(resultDetails != NULL)
8903                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8904         }
8905         if (resultDetails != NULL) {
8906             gameInfo.result = result;
8907             gameInfo.resultDetails = StrSave(resultDetails);
8908
8909             /* display last move only if game was not loaded from file */
8910             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8911                 DisplayMove(currentMove - 1);
8912     
8913             if (forwardMostMove != 0) {
8914                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8915                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8916                                                                 ) {
8917                     if (*appData.saveGameFile != NULLCHAR) {
8918                         SaveGameToFile(appData.saveGameFile, TRUE);
8919                     } else if (appData.autoSaveGames) {
8920                         AutoSaveGame();
8921                     }
8922                     if (*appData.savePositionFile != NULLCHAR) {
8923                         SavePositionToFile(appData.savePositionFile);
8924                     }
8925                 }
8926             }
8927
8928             /* Tell program how game ended in case it is learning */
8929             /* [HGM] Moved this to after saving the PGN, just in case */
8930             /* engine died and we got here through time loss. In that */
8931             /* case we will get a fatal error writing the pipe, which */
8932             /* would otherwise lose us the PGN.                       */
8933             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8934             /* output during GameEnds should never be fatal anymore   */
8935             if (gameMode == MachinePlaysWhite ||
8936                 gameMode == MachinePlaysBlack ||
8937                 gameMode == TwoMachinesPlay ||
8938                 gameMode == IcsPlayingWhite ||
8939                 gameMode == IcsPlayingBlack ||
8940                 gameMode == BeginningOfGame) {
8941                 char buf[MSG_SIZ];
8942                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8943                         resultDetails);
8944                 if (first.pr != NoProc) {
8945                     SendToProgram(buf, &first);
8946                 }
8947                 if (second.pr != NoProc &&
8948                     gameMode == TwoMachinesPlay) {
8949                     SendToProgram(buf, &second);
8950                 }
8951             }
8952         }
8953
8954         if (appData.icsActive) {
8955             if (appData.quietPlay &&
8956                 (gameMode == IcsPlayingWhite ||
8957                  gameMode == IcsPlayingBlack)) {
8958                 SendToICS(ics_prefix);
8959                 SendToICS("set shout 1\n");
8960             }
8961             nextGameMode = IcsIdle;
8962             ics_user_moved = FALSE;
8963             /* clean up premove.  It's ugly when the game has ended and the
8964              * premove highlights are still on the board.
8965              */
8966             if (gotPremove) {
8967               gotPremove = FALSE;
8968               ClearPremoveHighlights();
8969               DrawPosition(FALSE, boards[currentMove]);
8970             }
8971             if (whosays == GE_ICS) {
8972                 switch (result) {
8973                 case WhiteWins:
8974                     if (gameMode == IcsPlayingWhite)
8975                         PlayIcsWinSound();
8976                     else if(gameMode == IcsPlayingBlack)
8977                         PlayIcsLossSound();
8978                     break;
8979                 case BlackWins:
8980                     if (gameMode == IcsPlayingBlack)
8981                         PlayIcsWinSound();
8982                     else if(gameMode == IcsPlayingWhite)
8983                         PlayIcsLossSound();
8984                     break;
8985                 case GameIsDrawn:
8986                     PlayIcsDrawSound();
8987                     break;
8988                 default:
8989                     PlayIcsUnfinishedSound();
8990                 }
8991             }
8992         } else if (gameMode == EditGame ||
8993                    gameMode == PlayFromGameFile || 
8994                    gameMode == AnalyzeMode || 
8995                    gameMode == AnalyzeFile) {
8996             nextGameMode = gameMode;
8997         } else {
8998             nextGameMode = EndOfGame;
8999         }
9000         pausing = FALSE;
9001         ModeHighlight();
9002     } else {
9003         nextGameMode = gameMode;
9004     }
9005
9006     if (appData.noChessProgram) {
9007         gameMode = nextGameMode;
9008         ModeHighlight();
9009         endingGame = 0; /* [HGM] crash */
9010         return;
9011     }
9012
9013     if (first.reuse) {
9014         /* Put first chess program into idle state */
9015         if (first.pr != NoProc &&
9016             (gameMode == MachinePlaysWhite ||
9017              gameMode == MachinePlaysBlack ||
9018              gameMode == TwoMachinesPlay ||
9019              gameMode == IcsPlayingWhite ||
9020              gameMode == IcsPlayingBlack ||
9021              gameMode == BeginningOfGame)) {
9022             SendToProgram("force\n", &first);
9023             if (first.usePing) {
9024               char buf[MSG_SIZ];
9025               sprintf(buf, "ping %d\n", ++first.lastPing);
9026               SendToProgram(buf, &first);
9027             }
9028         }
9029     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9030         /* Kill off first chess program */
9031         if (first.isr != NULL)
9032           RemoveInputSource(first.isr);
9033         first.isr = NULL;
9034     
9035         if (first.pr != NoProc) {
9036             ExitAnalyzeMode();
9037             DoSleep( appData.delayBeforeQuit );
9038             SendToProgram("quit\n", &first);
9039             DoSleep( appData.delayAfterQuit );
9040             DestroyChildProcess(first.pr, first.useSigterm);
9041         }
9042         first.pr = NoProc;
9043     }
9044     if (second.reuse) {
9045         /* Put second chess program into idle state */
9046         if (second.pr != NoProc &&
9047             gameMode == TwoMachinesPlay) {
9048             SendToProgram("force\n", &second);
9049             if (second.usePing) {
9050               char buf[MSG_SIZ];
9051               sprintf(buf, "ping %d\n", ++second.lastPing);
9052               SendToProgram(buf, &second);
9053             }
9054         }
9055     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9056         /* Kill off second chess program */
9057         if (second.isr != NULL)
9058           RemoveInputSource(second.isr);
9059         second.isr = NULL;
9060     
9061         if (second.pr != NoProc) {
9062             DoSleep( appData.delayBeforeQuit );
9063             SendToProgram("quit\n", &second);
9064             DoSleep( appData.delayAfterQuit );
9065             DestroyChildProcess(second.pr, second.useSigterm);
9066         }
9067         second.pr = NoProc;
9068     }
9069
9070     if (matchMode && gameMode == TwoMachinesPlay) {
9071         switch (result) {
9072         case WhiteWins:
9073           if (first.twoMachinesColor[0] == 'w') {
9074             first.matchWins++;
9075           } else {
9076             second.matchWins++;
9077           }
9078           break;
9079         case BlackWins:
9080           if (first.twoMachinesColor[0] == 'b') {
9081             first.matchWins++;
9082           } else {
9083             second.matchWins++;
9084           }
9085           break;
9086         default:
9087           break;
9088         }
9089         if (matchGame < appData.matchGames) {
9090             char *tmp;
9091             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9092                 tmp = first.twoMachinesColor;
9093                 first.twoMachinesColor = second.twoMachinesColor;
9094                 second.twoMachinesColor = tmp;
9095             }
9096             gameMode = nextGameMode;
9097             matchGame++;
9098             if(appData.matchPause>10000 || appData.matchPause<10)
9099                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9100             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9101             endingGame = 0; /* [HGM] crash */
9102             return;
9103         } else {
9104             char buf[MSG_SIZ];
9105             gameMode = nextGameMode;
9106             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9107                     first.tidy, second.tidy,
9108                     first.matchWins, second.matchWins,
9109                     appData.matchGames - (first.matchWins + second.matchWins));
9110             DisplayFatalError(buf, 0, 0);
9111         }
9112     }
9113     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9114         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9115       ExitAnalyzeMode();
9116     gameMode = nextGameMode;
9117     ModeHighlight();
9118     endingGame = 0;  /* [HGM] crash */
9119 }
9120
9121 /* Assumes program was just initialized (initString sent).
9122    Leaves program in force mode. */
9123 void
9124 FeedMovesToProgram(cps, upto) 
9125      ChessProgramState *cps;
9126      int upto;
9127 {
9128     int i;
9129     
9130     if (appData.debugMode)
9131       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9132               startedFromSetupPosition ? "position and " : "",
9133               backwardMostMove, upto, cps->which);
9134     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9135         // [HGM] variantswitch: make engine aware of new variant
9136         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9137                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9138         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9139         SendToProgram(buf, cps);
9140         currentlyInitializedVariant = gameInfo.variant;
9141     }
9142     SendToProgram("force\n", cps);
9143     if (startedFromSetupPosition) {
9144         SendBoard(cps, backwardMostMove);
9145     if (appData.debugMode) {
9146         fprintf(debugFP, "feedMoves\n");
9147     }
9148     }
9149     for (i = backwardMostMove; i < upto; i++) {
9150         SendMoveToProgram(i, cps);
9151     }
9152 }
9153
9154
9155 void
9156 ResurrectChessProgram()
9157 {
9158      /* The chess program may have exited.
9159         If so, restart it and feed it all the moves made so far. */
9160
9161     if (appData.noChessProgram || first.pr != NoProc) return;
9162     
9163     StartChessProgram(&first);
9164     InitChessProgram(&first, FALSE);
9165     FeedMovesToProgram(&first, currentMove);
9166
9167     if (!first.sendTime) {
9168         /* can't tell gnuchess what its clock should read,
9169            so we bow to its notion. */
9170         ResetClocks();
9171         timeRemaining[0][currentMove] = whiteTimeRemaining;
9172         timeRemaining[1][currentMove] = blackTimeRemaining;
9173     }
9174
9175     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9176                 appData.icsEngineAnalyze) && first.analysisSupport) {
9177       SendToProgram("analyze\n", &first);
9178       first.analyzing = TRUE;
9179     }
9180 }
9181
9182 /*
9183  * Button procedures
9184  */
9185 void
9186 Reset(redraw, init)
9187      int redraw, init;
9188 {
9189     int i;
9190
9191     if (appData.debugMode) {
9192         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9193                 redraw, init, gameMode);
9194     }
9195     CleanupTail(); // [HGM] vari: delete any stored variations
9196     pausing = pauseExamInvalid = FALSE;
9197     startedFromSetupPosition = blackPlaysFirst = FALSE;
9198     firstMove = TRUE;
9199     whiteFlag = blackFlag = FALSE;
9200     userOfferedDraw = FALSE;
9201     hintRequested = bookRequested = FALSE;
9202     first.maybeThinking = FALSE;
9203     second.maybeThinking = FALSE;
9204     first.bookSuspend = FALSE; // [HGM] book
9205     second.bookSuspend = FALSE;
9206     thinkOutput[0] = NULLCHAR;
9207     lastHint[0] = NULLCHAR;
9208     ClearGameInfo(&gameInfo);
9209     gameInfo.variant = StringToVariant(appData.variant);
9210     ics_user_moved = ics_clock_paused = FALSE;
9211     ics_getting_history = H_FALSE;
9212     ics_gamenum = -1;
9213     white_holding[0] = black_holding[0] = NULLCHAR;
9214     ClearProgramStats();
9215     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9216     
9217     ResetFrontEnd();
9218     ClearHighlights();
9219     flipView = appData.flipView;
9220     ClearPremoveHighlights();
9221     gotPremove = FALSE;
9222     alarmSounded = FALSE;
9223
9224     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9225     if(appData.serverMovesName != NULL) {
9226         /* [HGM] prepare to make moves file for broadcasting */
9227         clock_t t = clock();
9228         if(serverMoves != NULL) fclose(serverMoves);
9229         serverMoves = fopen(appData.serverMovesName, "r");
9230         if(serverMoves != NULL) {
9231             fclose(serverMoves);
9232             /* delay 15 sec before overwriting, so all clients can see end */
9233             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9234         }
9235         serverMoves = fopen(appData.serverMovesName, "w");
9236     }
9237
9238     ExitAnalyzeMode();
9239     gameMode = BeginningOfGame;
9240     ModeHighlight();
9241     if(appData.icsActive) gameInfo.variant = VariantNormal;
9242     currentMove = forwardMostMove = backwardMostMove = 0;
9243     InitPosition(redraw);
9244     for (i = 0; i < MAX_MOVES; i++) {
9245         if (commentList[i] != NULL) {
9246             free(commentList[i]);
9247             commentList[i] = NULL;
9248         }
9249     }
9250     ResetClocks();
9251     timeRemaining[0][0] = whiteTimeRemaining;
9252     timeRemaining[1][0] = blackTimeRemaining;
9253     if (first.pr == NULL) {
9254         StartChessProgram(&first);
9255     }
9256     if (init) {
9257             InitChessProgram(&first, startedFromSetupPosition);
9258     }
9259     DisplayTitle("");
9260     DisplayMessage("", "");
9261     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9262     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9263 }
9264
9265 void
9266 AutoPlayGameLoop()
9267 {
9268     for (;;) {
9269         if (!AutoPlayOneMove())
9270           return;
9271         if (matchMode || appData.timeDelay == 0)
9272           continue;
9273         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9274           return;
9275         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9276         break;
9277     }
9278 }
9279
9280
9281 int
9282 AutoPlayOneMove()
9283 {
9284     int fromX, fromY, toX, toY;
9285
9286     if (appData.debugMode) {
9287       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9288     }
9289
9290     if (gameMode != PlayFromGameFile)
9291       return FALSE;
9292
9293     if (currentMove >= forwardMostMove) {
9294       gameMode = EditGame;
9295       ModeHighlight();
9296
9297       /* [AS] Clear current move marker at the end of a game */
9298       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9299
9300       return FALSE;
9301     }
9302     
9303     toX = moveList[currentMove][2] - AAA;
9304     toY = moveList[currentMove][3] - ONE;
9305
9306     if (moveList[currentMove][1] == '@') {
9307         if (appData.highlightLastMove) {
9308             SetHighlights(-1, -1, toX, toY);
9309         }
9310     } else {
9311         fromX = moveList[currentMove][0] - AAA;
9312         fromY = moveList[currentMove][1] - ONE;
9313
9314         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9315
9316         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9317
9318         if (appData.highlightLastMove) {
9319             SetHighlights(fromX, fromY, toX, toY);
9320         }
9321     }
9322     DisplayMove(currentMove);
9323     SendMoveToProgram(currentMove++, &first);
9324     DisplayBothClocks();
9325     DrawPosition(FALSE, boards[currentMove]);
9326     // [HGM] PV info: always display, routine tests if empty
9327     DisplayComment(currentMove - 1, commentList[currentMove]);
9328     return TRUE;
9329 }
9330
9331
9332 int
9333 LoadGameOneMove(readAhead)
9334      ChessMove readAhead;
9335 {
9336     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9337     char promoChar = NULLCHAR;
9338     ChessMove moveType;
9339     char move[MSG_SIZ];
9340     char *p, *q;
9341     
9342     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9343         gameMode != AnalyzeMode && gameMode != Training) {
9344         gameFileFP = NULL;
9345         return FALSE;
9346     }
9347     
9348     yyboardindex = forwardMostMove;
9349     if (readAhead != (ChessMove)0) {
9350       moveType = readAhead;
9351     } else {
9352       if (gameFileFP == NULL)
9353           return FALSE;
9354       moveType = (ChessMove) yylex();
9355     }
9356     
9357     done = FALSE;
9358     switch (moveType) {
9359       case Comment:
9360         if (appData.debugMode) 
9361           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9362         p = yy_text;
9363
9364         /* append the comment but don't display it */
9365         AppendComment(currentMove, p, FALSE);
9366         return TRUE;
9367
9368       case WhiteCapturesEnPassant:
9369       case BlackCapturesEnPassant:
9370       case WhitePromotionChancellor:
9371       case BlackPromotionChancellor:
9372       case WhitePromotionArchbishop:
9373       case BlackPromotionArchbishop:
9374       case WhitePromotionCentaur:
9375       case BlackPromotionCentaur:
9376       case WhitePromotionQueen:
9377       case BlackPromotionQueen:
9378       case WhitePromotionRook:
9379       case BlackPromotionRook:
9380       case WhitePromotionBishop:
9381       case BlackPromotionBishop:
9382       case WhitePromotionKnight:
9383       case BlackPromotionKnight:
9384       case WhitePromotionKing:
9385       case BlackPromotionKing:
9386       case NormalMove:
9387       case WhiteKingSideCastle:
9388       case WhiteQueenSideCastle:
9389       case BlackKingSideCastle:
9390       case BlackQueenSideCastle:
9391       case WhiteKingSideCastleWild:
9392       case WhiteQueenSideCastleWild:
9393       case BlackKingSideCastleWild:
9394       case BlackQueenSideCastleWild:
9395       /* PUSH Fabien */
9396       case WhiteHSideCastleFR:
9397       case WhiteASideCastleFR:
9398       case BlackHSideCastleFR:
9399       case BlackASideCastleFR:
9400       /* POP Fabien */
9401         if (appData.debugMode)
9402           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9403         fromX = currentMoveString[0] - AAA;
9404         fromY = currentMoveString[1] - ONE;
9405         toX = currentMoveString[2] - AAA;
9406         toY = currentMoveString[3] - ONE;
9407         promoChar = currentMoveString[4];
9408         break;
9409
9410       case WhiteDrop:
9411       case BlackDrop:
9412         if (appData.debugMode)
9413           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9414         fromX = moveType == WhiteDrop ?
9415           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9416         (int) CharToPiece(ToLower(currentMoveString[0]));
9417         fromY = DROP_RANK;
9418         toX = currentMoveString[2] - AAA;
9419         toY = currentMoveString[3] - ONE;
9420         break;
9421
9422       case WhiteWins:
9423       case BlackWins:
9424       case GameIsDrawn:
9425       case GameUnfinished:
9426         if (appData.debugMode)
9427           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9428         p = strchr(yy_text, '{');
9429         if (p == NULL) p = strchr(yy_text, '(');
9430         if (p == NULL) {
9431             p = yy_text;
9432             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9433         } else {
9434             q = strchr(p, *p == '{' ? '}' : ')');
9435             if (q != NULL) *q = NULLCHAR;
9436             p++;
9437         }
9438         GameEnds(moveType, p, GE_FILE);
9439         done = TRUE;
9440         if (cmailMsgLoaded) {
9441             ClearHighlights();
9442             flipView = WhiteOnMove(currentMove);
9443             if (moveType == GameUnfinished) flipView = !flipView;
9444             if (appData.debugMode)
9445               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9446         }
9447         break;
9448
9449       case (ChessMove) 0:       /* end of file */
9450         if (appData.debugMode)
9451           fprintf(debugFP, "Parser hit end of file\n");
9452         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9453           case MT_NONE:
9454           case MT_CHECK:
9455             break;
9456           case MT_CHECKMATE:
9457           case MT_STAINMATE:
9458             if (WhiteOnMove(currentMove)) {
9459                 GameEnds(BlackWins, "Black mates", GE_FILE);
9460             } else {
9461                 GameEnds(WhiteWins, "White mates", GE_FILE);
9462             }
9463             break;
9464           case MT_STALEMATE:
9465             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9466             break;
9467         }
9468         done = TRUE;
9469         break;
9470
9471       case MoveNumberOne:
9472         if (lastLoadGameStart == GNUChessGame) {
9473             /* GNUChessGames have numbers, but they aren't move numbers */
9474             if (appData.debugMode)
9475               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9476                       yy_text, (int) moveType);
9477             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9478         }
9479         /* else fall thru */
9480
9481       case XBoardGame:
9482       case GNUChessGame:
9483       case PGNTag:
9484         /* Reached start of next game in file */
9485         if (appData.debugMode)
9486           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9487         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9488           case MT_NONE:
9489           case MT_CHECK:
9490             break;
9491           case MT_CHECKMATE:
9492           case MT_STAINMATE:
9493             if (WhiteOnMove(currentMove)) {
9494                 GameEnds(BlackWins, "Black mates", GE_FILE);
9495             } else {
9496                 GameEnds(WhiteWins, "White mates", GE_FILE);
9497             }
9498             break;
9499           case MT_STALEMATE:
9500             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9501             break;
9502         }
9503         done = TRUE;
9504         break;
9505
9506       case PositionDiagram:     /* should not happen; ignore */
9507       case ElapsedTime:         /* ignore */
9508       case NAG:                 /* ignore */
9509         if (appData.debugMode)
9510           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9511                   yy_text, (int) moveType);
9512         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9513
9514       case IllegalMove:
9515         if (appData.testLegality) {
9516             if (appData.debugMode)
9517               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9518             sprintf(move, _("Illegal move: %d.%s%s"),
9519                     (forwardMostMove / 2) + 1,
9520                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9521             DisplayError(move, 0);
9522             done = TRUE;
9523         } else {
9524             if (appData.debugMode)
9525               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9526                       yy_text, currentMoveString);
9527             fromX = currentMoveString[0] - AAA;
9528             fromY = currentMoveString[1] - ONE;
9529             toX = currentMoveString[2] - AAA;
9530             toY = currentMoveString[3] - ONE;
9531             promoChar = currentMoveString[4];
9532         }
9533         break;
9534
9535       case AmbiguousMove:
9536         if (appData.debugMode)
9537           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9538         sprintf(move, _("Ambiguous move: %d.%s%s"),
9539                 (forwardMostMove / 2) + 1,
9540                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9541         DisplayError(move, 0);
9542         done = TRUE;
9543         break;
9544
9545       default:
9546       case ImpossibleMove:
9547         if (appData.debugMode)
9548           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9549         sprintf(move, _("Illegal move: %d.%s%s"),
9550                 (forwardMostMove / 2) + 1,
9551                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9552         DisplayError(move, 0);
9553         done = TRUE;
9554         break;
9555     }
9556
9557     if (done) {
9558         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9559             DrawPosition(FALSE, boards[currentMove]);
9560             DisplayBothClocks();
9561             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9562               DisplayComment(currentMove - 1, commentList[currentMove]);
9563         }
9564         (void) StopLoadGameTimer();
9565         gameFileFP = NULL;
9566         cmailOldMove = forwardMostMove;
9567         return FALSE;
9568     } else {
9569         /* currentMoveString is set as a side-effect of yylex */
9570         strcat(currentMoveString, "\n");
9571         strcpy(moveList[forwardMostMove], currentMoveString);
9572         
9573         thinkOutput[0] = NULLCHAR;
9574         MakeMove(fromX, fromY, toX, toY, promoChar);
9575         currentMove = forwardMostMove;
9576         return TRUE;
9577     }
9578 }
9579
9580 /* Load the nth game from the given file */
9581 int
9582 LoadGameFromFile(filename, n, title, useList)
9583      char *filename;
9584      int n;
9585      char *title;
9586      /*Boolean*/ int useList;
9587 {
9588     FILE *f;
9589     char buf[MSG_SIZ];
9590
9591     if (strcmp(filename, "-") == 0) {
9592         f = stdin;
9593         title = "stdin";
9594     } else {
9595         f = fopen(filename, "rb");
9596         if (f == NULL) {
9597           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9598             DisplayError(buf, errno);
9599             return FALSE;
9600         }
9601     }
9602     if (fseek(f, 0, 0) == -1) {
9603         /* f is not seekable; probably a pipe */
9604         useList = FALSE;
9605     }
9606     if (useList && n == 0) {
9607         int error = GameListBuild(f);
9608         if (error) {
9609             DisplayError(_("Cannot build game list"), error);
9610         } else if (!ListEmpty(&gameList) &&
9611                    ((ListGame *) gameList.tailPred)->number > 1) {
9612             GameListPopUp(f, title);
9613             return TRUE;
9614         }
9615         GameListDestroy();
9616         n = 1;
9617     }
9618     if (n == 0) n = 1;
9619     return LoadGame(f, n, title, FALSE);
9620 }
9621
9622
9623 void
9624 MakeRegisteredMove()
9625 {
9626     int fromX, fromY, toX, toY;
9627     char promoChar;
9628     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9629         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9630           case CMAIL_MOVE:
9631           case CMAIL_DRAW:
9632             if (appData.debugMode)
9633               fprintf(debugFP, "Restoring %s for game %d\n",
9634                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9635     
9636             thinkOutput[0] = NULLCHAR;
9637             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9638             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9639             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9640             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9641             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9642             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9643             MakeMove(fromX, fromY, toX, toY, promoChar);
9644             ShowMove(fromX, fromY, toX, toY);
9645               
9646             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9647               case MT_NONE:
9648               case MT_CHECK:
9649                 break;
9650                 
9651               case MT_CHECKMATE:
9652               case MT_STAINMATE:
9653                 if (WhiteOnMove(currentMove)) {
9654                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9655                 } else {
9656                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9657                 }
9658                 break;
9659                 
9660               case MT_STALEMATE:
9661                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9662                 break;
9663             }
9664
9665             break;
9666             
9667           case CMAIL_RESIGN:
9668             if (WhiteOnMove(currentMove)) {
9669                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9670             } else {
9671                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9672             }
9673             break;
9674             
9675           case CMAIL_ACCEPT:
9676             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9677             break;
9678               
9679           default:
9680             break;
9681         }
9682     }
9683
9684     return;
9685 }
9686
9687 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9688 int
9689 CmailLoadGame(f, gameNumber, title, useList)
9690      FILE *f;
9691      int gameNumber;
9692      char *title;
9693      int useList;
9694 {
9695     int retVal;
9696
9697     if (gameNumber > nCmailGames) {
9698         DisplayError(_("No more games in this message"), 0);
9699         return FALSE;
9700     }
9701     if (f == lastLoadGameFP) {
9702         int offset = gameNumber - lastLoadGameNumber;
9703         if (offset == 0) {
9704             cmailMsg[0] = NULLCHAR;
9705             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9706                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9707                 nCmailMovesRegistered--;
9708             }
9709             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9710             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9711                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9712             }
9713         } else {
9714             if (! RegisterMove()) return FALSE;
9715         }
9716     }
9717
9718     retVal = LoadGame(f, gameNumber, title, useList);
9719
9720     /* Make move registered during previous look at this game, if any */
9721     MakeRegisteredMove();
9722
9723     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9724         commentList[currentMove]
9725           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9726         DisplayComment(currentMove - 1, commentList[currentMove]);
9727     }
9728
9729     return retVal;
9730 }
9731
9732 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9733 int
9734 ReloadGame(offset)
9735      int offset;
9736 {
9737     int gameNumber = lastLoadGameNumber + offset;
9738     if (lastLoadGameFP == NULL) {
9739         DisplayError(_("No game has been loaded yet"), 0);
9740         return FALSE;
9741     }
9742     if (gameNumber <= 0) {
9743         DisplayError(_("Can't back up any further"), 0);
9744         return FALSE;
9745     }
9746     if (cmailMsgLoaded) {
9747         return CmailLoadGame(lastLoadGameFP, gameNumber,
9748                              lastLoadGameTitle, lastLoadGameUseList);
9749     } else {
9750         return LoadGame(lastLoadGameFP, gameNumber,
9751                         lastLoadGameTitle, lastLoadGameUseList);
9752     }
9753 }
9754
9755
9756
9757 /* Load the nth game from open file f */
9758 int
9759 LoadGame(f, gameNumber, title, useList)
9760      FILE *f;
9761      int gameNumber;
9762      char *title;
9763      int useList;
9764 {
9765     ChessMove cm;
9766     char buf[MSG_SIZ];
9767     int gn = gameNumber;
9768     ListGame *lg = NULL;
9769     int numPGNTags = 0;
9770     int err;
9771     GameMode oldGameMode;
9772     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9773
9774     if (appData.debugMode) 
9775         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9776
9777     if (gameMode == Training )
9778         SetTrainingModeOff();
9779
9780     oldGameMode = gameMode;
9781     if (gameMode != BeginningOfGame) {
9782       Reset(FALSE, TRUE);
9783     }
9784
9785     gameFileFP = f;
9786     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9787         fclose(lastLoadGameFP);
9788     }
9789
9790     if (useList) {
9791         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9792         
9793         if (lg) {
9794             fseek(f, lg->offset, 0);
9795             GameListHighlight(gameNumber);
9796             gn = 1;
9797         }
9798         else {
9799             DisplayError(_("Game number out of range"), 0);
9800             return FALSE;
9801         }
9802     } else {
9803         GameListDestroy();
9804         if (fseek(f, 0, 0) == -1) {
9805             if (f == lastLoadGameFP ?
9806                 gameNumber == lastLoadGameNumber + 1 :
9807                 gameNumber == 1) {
9808                 gn = 1;
9809             } else {
9810                 DisplayError(_("Can't seek on game file"), 0);
9811                 return FALSE;
9812             }
9813         }
9814     }
9815     lastLoadGameFP = f;
9816     lastLoadGameNumber = gameNumber;
9817     strcpy(lastLoadGameTitle, title);
9818     lastLoadGameUseList = useList;
9819
9820     yynewfile(f);
9821
9822     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9823       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9824                 lg->gameInfo.black);
9825             DisplayTitle(buf);
9826     } else if (*title != NULLCHAR) {
9827         if (gameNumber > 1) {
9828             sprintf(buf, "%s %d", title, gameNumber);
9829             DisplayTitle(buf);
9830         } else {
9831             DisplayTitle(title);
9832         }
9833     }
9834
9835     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9836         gameMode = PlayFromGameFile;
9837         ModeHighlight();
9838     }
9839
9840     currentMove = forwardMostMove = backwardMostMove = 0;
9841     CopyBoard(boards[0], initialPosition);
9842     StopClocks();
9843
9844     /*
9845      * Skip the first gn-1 games in the file.
9846      * Also skip over anything that precedes an identifiable 
9847      * start of game marker, to avoid being confused by 
9848      * garbage at the start of the file.  Currently 
9849      * recognized start of game markers are the move number "1",
9850      * the pattern "gnuchess .* game", the pattern
9851      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9852      * A game that starts with one of the latter two patterns
9853      * will also have a move number 1, possibly
9854      * following a position diagram.
9855      * 5-4-02: Let's try being more lenient and allowing a game to
9856      * start with an unnumbered move.  Does that break anything?
9857      */
9858     cm = lastLoadGameStart = (ChessMove) 0;
9859     while (gn > 0) {
9860         yyboardindex = forwardMostMove;
9861         cm = (ChessMove) yylex();
9862         switch (cm) {
9863           case (ChessMove) 0:
9864             if (cmailMsgLoaded) {
9865                 nCmailGames = CMAIL_MAX_GAMES - gn;
9866             } else {
9867                 Reset(TRUE, TRUE);
9868                 DisplayError(_("Game not found in file"), 0);
9869             }
9870             return FALSE;
9871
9872           case GNUChessGame:
9873           case XBoardGame:
9874             gn--;
9875             lastLoadGameStart = cm;
9876             break;
9877             
9878           case MoveNumberOne:
9879             switch (lastLoadGameStart) {
9880               case GNUChessGame:
9881               case XBoardGame:
9882               case PGNTag:
9883                 break;
9884               case MoveNumberOne:
9885               case (ChessMove) 0:
9886                 gn--;           /* count this game */
9887                 lastLoadGameStart = cm;
9888                 break;
9889               default:
9890                 /* impossible */
9891                 break;
9892             }
9893             break;
9894
9895           case PGNTag:
9896             switch (lastLoadGameStart) {
9897               case GNUChessGame:
9898               case PGNTag:
9899               case MoveNumberOne:
9900               case (ChessMove) 0:
9901                 gn--;           /* count this game */
9902                 lastLoadGameStart = cm;
9903                 break;
9904               case XBoardGame:
9905                 lastLoadGameStart = cm; /* game counted already */
9906                 break;
9907               default:
9908                 /* impossible */
9909                 break;
9910             }
9911             if (gn > 0) {
9912                 do {
9913                     yyboardindex = forwardMostMove;
9914                     cm = (ChessMove) yylex();
9915                 } while (cm == PGNTag || cm == Comment);
9916             }
9917             break;
9918
9919           case WhiteWins:
9920           case BlackWins:
9921           case GameIsDrawn:
9922             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9923                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9924                     != CMAIL_OLD_RESULT) {
9925                     nCmailResults ++ ;
9926                     cmailResult[  CMAIL_MAX_GAMES
9927                                 - gn - 1] = CMAIL_OLD_RESULT;
9928                 }
9929             }
9930             break;
9931
9932           case NormalMove:
9933             /* Only a NormalMove can be at the start of a game
9934              * without a position diagram. */
9935             if (lastLoadGameStart == (ChessMove) 0) {
9936               gn--;
9937               lastLoadGameStart = MoveNumberOne;
9938             }
9939             break;
9940
9941           default:
9942             break;
9943         }
9944     }
9945     
9946     if (appData.debugMode)
9947       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9948
9949     if (cm == XBoardGame) {
9950         /* Skip any header junk before position diagram and/or move 1 */
9951         for (;;) {
9952             yyboardindex = forwardMostMove;
9953             cm = (ChessMove) yylex();
9954
9955             if (cm == (ChessMove) 0 ||
9956                 cm == GNUChessGame || cm == XBoardGame) {
9957                 /* Empty game; pretend end-of-file and handle later */
9958                 cm = (ChessMove) 0;
9959                 break;
9960             }
9961
9962             if (cm == MoveNumberOne || cm == PositionDiagram ||
9963                 cm == PGNTag || cm == Comment)
9964               break;
9965         }
9966     } else if (cm == GNUChessGame) {
9967         if (gameInfo.event != NULL) {
9968             free(gameInfo.event);
9969         }
9970         gameInfo.event = StrSave(yy_text);
9971     }   
9972
9973     startedFromSetupPosition = FALSE;
9974     while (cm == PGNTag) {
9975         if (appData.debugMode) 
9976           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9977         err = ParsePGNTag(yy_text, &gameInfo);
9978         if (!err) numPGNTags++;
9979
9980         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9981         if(gameInfo.variant != oldVariant) {
9982             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9983             InitPosition(TRUE);
9984             oldVariant = gameInfo.variant;
9985             if (appData.debugMode) 
9986               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9987         }
9988
9989
9990         if (gameInfo.fen != NULL) {
9991           Board initial_position;
9992           startedFromSetupPosition = TRUE;
9993           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9994             Reset(TRUE, TRUE);
9995             DisplayError(_("Bad FEN position in file"), 0);
9996             return FALSE;
9997           }
9998           CopyBoard(boards[0], initial_position);
9999           if (blackPlaysFirst) {
10000             currentMove = forwardMostMove = backwardMostMove = 1;
10001             CopyBoard(boards[1], initial_position);
10002             strcpy(moveList[0], "");
10003             strcpy(parseList[0], "");
10004             timeRemaining[0][1] = whiteTimeRemaining;
10005             timeRemaining[1][1] = blackTimeRemaining;
10006             if (commentList[0] != NULL) {
10007               commentList[1] = commentList[0];
10008               commentList[0] = NULL;
10009             }
10010           } else {
10011             currentMove = forwardMostMove = backwardMostMove = 0;
10012           }
10013           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10014           {   int i;
10015               initialRulePlies = FENrulePlies;
10016               for( i=0; i< nrCastlingRights; i++ )
10017                   initialRights[i] = initial_position[CASTLING][i];
10018           }
10019           yyboardindex = forwardMostMove;
10020           free(gameInfo.fen);
10021           gameInfo.fen = NULL;
10022         }
10023
10024         yyboardindex = forwardMostMove;
10025         cm = (ChessMove) yylex();
10026
10027         /* Handle comments interspersed among the tags */
10028         while (cm == Comment) {
10029             char *p;
10030             if (appData.debugMode) 
10031               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10032             p = yy_text;
10033             AppendComment(currentMove, p, FALSE);
10034             yyboardindex = forwardMostMove;
10035             cm = (ChessMove) yylex();
10036         }
10037     }
10038
10039     /* don't rely on existence of Event tag since if game was
10040      * pasted from clipboard the Event tag may not exist
10041      */
10042     if (numPGNTags > 0){
10043         char *tags;
10044         if (gameInfo.variant == VariantNormal) {
10045           gameInfo.variant = StringToVariant(gameInfo.event);
10046         }
10047         if (!matchMode) {
10048           if( appData.autoDisplayTags ) {
10049             tags = PGNTags(&gameInfo);
10050             TagsPopUp(tags, CmailMsg());
10051             free(tags);
10052           }
10053         }
10054     } else {
10055         /* Make something up, but don't display it now */
10056         SetGameInfo();
10057         TagsPopDown();
10058     }
10059
10060     if (cm == PositionDiagram) {
10061         int i, j;
10062         char *p;
10063         Board initial_position;
10064
10065         if (appData.debugMode)
10066           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10067
10068         if (!startedFromSetupPosition) {
10069             p = yy_text;
10070             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10071               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10072                 switch (*p) {
10073                   case '[':
10074                   case '-':
10075                   case ' ':
10076                   case '\t':
10077                   case '\n':
10078                   case '\r':
10079                     break;
10080                   default:
10081                     initial_position[i][j++] = CharToPiece(*p);
10082                     break;
10083                 }
10084             while (*p == ' ' || *p == '\t' ||
10085                    *p == '\n' || *p == '\r') p++;
10086         
10087             if (strncmp(p, "black", strlen("black"))==0)
10088               blackPlaysFirst = TRUE;
10089             else
10090               blackPlaysFirst = FALSE;
10091             startedFromSetupPosition = TRUE;
10092         
10093             CopyBoard(boards[0], initial_position);
10094             if (blackPlaysFirst) {
10095                 currentMove = forwardMostMove = backwardMostMove = 1;
10096                 CopyBoard(boards[1], initial_position);
10097                 strcpy(moveList[0], "");
10098                 strcpy(parseList[0], "");
10099                 timeRemaining[0][1] = whiteTimeRemaining;
10100                 timeRemaining[1][1] = blackTimeRemaining;
10101                 if (commentList[0] != NULL) {
10102                     commentList[1] = commentList[0];
10103                     commentList[0] = NULL;
10104                 }
10105             } else {
10106                 currentMove = forwardMostMove = backwardMostMove = 0;
10107             }
10108         }
10109         yyboardindex = forwardMostMove;
10110         cm = (ChessMove) yylex();
10111     }
10112
10113     if (first.pr == NoProc) {
10114         StartChessProgram(&first);
10115     }
10116     InitChessProgram(&first, FALSE);
10117     SendToProgram("force\n", &first);
10118     if (startedFromSetupPosition) {
10119         SendBoard(&first, forwardMostMove);
10120     if (appData.debugMode) {
10121         fprintf(debugFP, "Load Game\n");
10122     }
10123         DisplayBothClocks();
10124     }      
10125
10126     /* [HGM] server: flag to write setup moves in broadcast file as one */
10127     loadFlag = appData.suppressLoadMoves;
10128
10129     while (cm == Comment) {
10130         char *p;
10131         if (appData.debugMode) 
10132           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10133         p = yy_text;
10134         AppendComment(currentMove, p, FALSE);
10135         yyboardindex = forwardMostMove;
10136         cm = (ChessMove) yylex();
10137     }
10138
10139     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10140         cm == WhiteWins || cm == BlackWins ||
10141         cm == GameIsDrawn || cm == GameUnfinished) {
10142         DisplayMessage("", _("No moves in game"));
10143         if (cmailMsgLoaded) {
10144             if (appData.debugMode)
10145               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10146             ClearHighlights();
10147             flipView = FALSE;
10148         }
10149         DrawPosition(FALSE, boards[currentMove]);
10150         DisplayBothClocks();
10151         gameMode = EditGame;
10152         ModeHighlight();
10153         gameFileFP = NULL;
10154         cmailOldMove = 0;
10155         return TRUE;
10156     }
10157
10158     // [HGM] PV info: routine tests if comment empty
10159     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10160         DisplayComment(currentMove - 1, commentList[currentMove]);
10161     }
10162     if (!matchMode && appData.timeDelay != 0) 
10163       DrawPosition(FALSE, boards[currentMove]);
10164
10165     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10166       programStats.ok_to_send = 1;
10167     }
10168
10169     /* if the first token after the PGN tags is a move
10170      * and not move number 1, retrieve it from the parser 
10171      */
10172     if (cm != MoveNumberOne)
10173         LoadGameOneMove(cm);
10174
10175     /* load the remaining moves from the file */
10176     while (LoadGameOneMove((ChessMove)0)) {
10177       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10178       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10179     }
10180
10181     /* rewind to the start of the game */
10182     currentMove = backwardMostMove;
10183
10184     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10185
10186     if (oldGameMode == AnalyzeFile ||
10187         oldGameMode == AnalyzeMode) {
10188       AnalyzeFileEvent();
10189     }
10190
10191     if (matchMode || appData.timeDelay == 0) {
10192       ToEndEvent();
10193       gameMode = EditGame;
10194       ModeHighlight();
10195     } else if (appData.timeDelay > 0) {
10196       AutoPlayGameLoop();
10197     }
10198
10199     if (appData.debugMode) 
10200         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10201
10202     loadFlag = 0; /* [HGM] true game starts */
10203     return TRUE;
10204 }
10205
10206 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10207 int
10208 ReloadPosition(offset)
10209      int offset;
10210 {
10211     int positionNumber = lastLoadPositionNumber + offset;
10212     if (lastLoadPositionFP == NULL) {
10213         DisplayError(_("No position has been loaded yet"), 0);
10214         return FALSE;
10215     }
10216     if (positionNumber <= 0) {
10217         DisplayError(_("Can't back up any further"), 0);
10218         return FALSE;
10219     }
10220     return LoadPosition(lastLoadPositionFP, positionNumber,
10221                         lastLoadPositionTitle);
10222 }
10223
10224 /* Load the nth position from the given file */
10225 int
10226 LoadPositionFromFile(filename, n, title)
10227      char *filename;
10228      int n;
10229      char *title;
10230 {
10231     FILE *f;
10232     char buf[MSG_SIZ];
10233
10234     if (strcmp(filename, "-") == 0) {
10235         return LoadPosition(stdin, n, "stdin");
10236     } else {
10237         f = fopen(filename, "rb");
10238         if (f == NULL) {
10239             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10240             DisplayError(buf, errno);
10241             return FALSE;
10242         } else {
10243             return LoadPosition(f, n, title);
10244         }
10245     }
10246 }
10247
10248 /* Load the nth position from the given open file, and close it */
10249 int
10250 LoadPosition(f, positionNumber, title)
10251      FILE *f;
10252      int positionNumber;
10253      char *title;
10254 {
10255     char *p, line[MSG_SIZ];
10256     Board initial_position;
10257     int i, j, fenMode, pn;
10258     
10259     if (gameMode == Training )
10260         SetTrainingModeOff();
10261
10262     if (gameMode != BeginningOfGame) {
10263         Reset(FALSE, TRUE);
10264     }
10265     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10266         fclose(lastLoadPositionFP);
10267     }
10268     if (positionNumber == 0) positionNumber = 1;
10269     lastLoadPositionFP = f;
10270     lastLoadPositionNumber = positionNumber;
10271     strcpy(lastLoadPositionTitle, title);
10272     if (first.pr == NoProc) {
10273       StartChessProgram(&first);
10274       InitChessProgram(&first, FALSE);
10275     }    
10276     pn = positionNumber;
10277     if (positionNumber < 0) {
10278         /* Negative position number means to seek to that byte offset */
10279         if (fseek(f, -positionNumber, 0) == -1) {
10280             DisplayError(_("Can't seek on position file"), 0);
10281             return FALSE;
10282         };
10283         pn = 1;
10284     } else {
10285         if (fseek(f, 0, 0) == -1) {
10286             if (f == lastLoadPositionFP ?
10287                 positionNumber == lastLoadPositionNumber + 1 :
10288                 positionNumber == 1) {
10289                 pn = 1;
10290             } else {
10291                 DisplayError(_("Can't seek on position file"), 0);
10292                 return FALSE;
10293             }
10294         }
10295     }
10296     /* See if this file is FEN or old-style xboard */
10297     if (fgets(line, MSG_SIZ, f) == NULL) {
10298         DisplayError(_("Position not found in file"), 0);
10299         return FALSE;
10300     }
10301     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10302     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10303
10304     if (pn >= 2) {
10305         if (fenMode || line[0] == '#') pn--;
10306         while (pn > 0) {
10307             /* skip positions before number pn */
10308             if (fgets(line, MSG_SIZ, f) == NULL) {
10309                 Reset(TRUE, TRUE);
10310                 DisplayError(_("Position not found in file"), 0);
10311                 return FALSE;
10312             }
10313             if (fenMode || line[0] == '#') pn--;
10314         }
10315     }
10316
10317     if (fenMode) {
10318         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10319             DisplayError(_("Bad FEN position in file"), 0);
10320             return FALSE;
10321         }
10322     } else {
10323         (void) fgets(line, MSG_SIZ, f);
10324         (void) fgets(line, MSG_SIZ, f);
10325     
10326         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10327             (void) fgets(line, MSG_SIZ, f);
10328             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10329                 if (*p == ' ')
10330                   continue;
10331                 initial_position[i][j++] = CharToPiece(*p);
10332             }
10333         }
10334     
10335         blackPlaysFirst = FALSE;
10336         if (!feof(f)) {
10337             (void) fgets(line, MSG_SIZ, f);
10338             if (strncmp(line, "black", strlen("black"))==0)
10339               blackPlaysFirst = TRUE;
10340         }
10341     }
10342     startedFromSetupPosition = TRUE;
10343     
10344     SendToProgram("force\n", &first);
10345     CopyBoard(boards[0], initial_position);
10346     if (blackPlaysFirst) {
10347         currentMove = forwardMostMove = backwardMostMove = 1;
10348         strcpy(moveList[0], "");
10349         strcpy(parseList[0], "");
10350         CopyBoard(boards[1], initial_position);
10351         DisplayMessage("", _("Black to play"));
10352     } else {
10353         currentMove = forwardMostMove = backwardMostMove = 0;
10354         DisplayMessage("", _("White to play"));
10355     }
10356     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10357     SendBoard(&first, forwardMostMove);
10358     if (appData.debugMode) {
10359 int i, j;
10360   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10361   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10362         fprintf(debugFP, "Load Position\n");
10363     }
10364
10365     if (positionNumber > 1) {
10366         sprintf(line, "%s %d", title, positionNumber);
10367         DisplayTitle(line);
10368     } else {
10369         DisplayTitle(title);
10370     }
10371     gameMode = EditGame;
10372     ModeHighlight();
10373     ResetClocks();
10374     timeRemaining[0][1] = whiteTimeRemaining;
10375     timeRemaining[1][1] = blackTimeRemaining;
10376     DrawPosition(FALSE, boards[currentMove]);
10377    
10378     return TRUE;
10379 }
10380
10381
10382 void
10383 CopyPlayerNameIntoFileName(dest, src)
10384      char **dest, *src;
10385 {
10386     while (*src != NULLCHAR && *src != ',') {
10387         if (*src == ' ') {
10388             *(*dest)++ = '_';
10389             src++;
10390         } else {
10391             *(*dest)++ = *src++;
10392         }
10393     }
10394 }
10395
10396 char *DefaultFileName(ext)
10397      char *ext;
10398 {
10399     static char def[MSG_SIZ];
10400     char *p;
10401
10402     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10403         p = def;
10404         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10405         *p++ = '-';
10406         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10407         *p++ = '.';
10408         strcpy(p, ext);
10409     } else {
10410         def[0] = NULLCHAR;
10411     }
10412     return def;
10413 }
10414
10415 /* Save the current game to the given file */
10416 int
10417 SaveGameToFile(filename, append)
10418      char *filename;
10419      int append;
10420 {
10421     FILE *f;
10422     char buf[MSG_SIZ];
10423
10424     if (strcmp(filename, "-") == 0) {
10425         return SaveGame(stdout, 0, NULL);
10426     } else {
10427         f = fopen(filename, append ? "a" : "w");
10428         if (f == NULL) {
10429             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10430             DisplayError(buf, errno);
10431             return FALSE;
10432         } else {
10433             return SaveGame(f, 0, NULL);
10434         }
10435     }
10436 }
10437
10438 char *
10439 SavePart(str)
10440      char *str;
10441 {
10442     static char buf[MSG_SIZ];
10443     char *p;
10444     
10445     p = strchr(str, ' ');
10446     if (p == NULL) return str;
10447     strncpy(buf, str, p - str);
10448     buf[p - str] = NULLCHAR;
10449     return buf;
10450 }
10451
10452 #define PGN_MAX_LINE 75
10453
10454 #define PGN_SIDE_WHITE  0
10455 #define PGN_SIDE_BLACK  1
10456
10457 /* [AS] */
10458 static int FindFirstMoveOutOfBook( int side )
10459 {
10460     int result = -1;
10461
10462     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10463         int index = backwardMostMove;
10464         int has_book_hit = 0;
10465
10466         if( (index % 2) != side ) {
10467             index++;
10468         }
10469
10470         while( index < forwardMostMove ) {
10471             /* Check to see if engine is in book */
10472             int depth = pvInfoList[index].depth;
10473             int score = pvInfoList[index].score;
10474             int in_book = 0;
10475
10476             if( depth <= 2 ) {
10477                 in_book = 1;
10478             }
10479             else if( score == 0 && depth == 63 ) {
10480                 in_book = 1; /* Zappa */
10481             }
10482             else if( score == 2 && depth == 99 ) {
10483                 in_book = 1; /* Abrok */
10484             }
10485
10486             has_book_hit += in_book;
10487
10488             if( ! in_book ) {
10489                 result = index;
10490
10491                 break;
10492             }
10493
10494             index += 2;
10495         }
10496     }
10497
10498     return result;
10499 }
10500
10501 /* [AS] */
10502 void GetOutOfBookInfo( char * buf )
10503 {
10504     int oob[2];
10505     int i;
10506     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10507
10508     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10509     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10510
10511     *buf = '\0';
10512
10513     if( oob[0] >= 0 || oob[1] >= 0 ) {
10514         for( i=0; i<2; i++ ) {
10515             int idx = oob[i];
10516
10517             if( idx >= 0 ) {
10518                 if( i > 0 && oob[0] >= 0 ) {
10519                     strcat( buf, "   " );
10520                 }
10521
10522                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10523                 sprintf( buf+strlen(buf), "%s%.2f", 
10524                     pvInfoList[idx].score >= 0 ? "+" : "",
10525                     pvInfoList[idx].score / 100.0 );
10526             }
10527         }
10528     }
10529 }
10530
10531 /* Save game in PGN style and close the file */
10532 int
10533 SaveGamePGN(f)
10534      FILE *f;
10535 {
10536     int i, offset, linelen, newblock;
10537     time_t tm;
10538 //    char *movetext;
10539     char numtext[32];
10540     int movelen, numlen, blank;
10541     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10542
10543     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10544     
10545     tm = time((time_t *) NULL);
10546     
10547     PrintPGNTags(f, &gameInfo);
10548     
10549     if (backwardMostMove > 0 || startedFromSetupPosition) {
10550         char *fen = PositionToFEN(backwardMostMove, NULL);
10551         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10552         fprintf(f, "\n{--------------\n");
10553         PrintPosition(f, backwardMostMove);
10554         fprintf(f, "--------------}\n");
10555         free(fen);
10556     }
10557     else {
10558         /* [AS] Out of book annotation */
10559         if( appData.saveOutOfBookInfo ) {
10560             char buf[64];
10561
10562             GetOutOfBookInfo( buf );
10563
10564             if( buf[0] != '\0' ) {
10565                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10566             }
10567         }
10568
10569         fprintf(f, "\n");
10570     }
10571
10572     i = backwardMostMove;
10573     linelen = 0;
10574     newblock = TRUE;
10575
10576     while (i < forwardMostMove) {
10577         /* Print comments preceding this move */
10578         if (commentList[i] != NULL) {
10579             if (linelen > 0) fprintf(f, "\n");
10580             fprintf(f, "%s", commentList[i]);
10581             linelen = 0;
10582             newblock = TRUE;
10583         }
10584
10585         /* Format move number */
10586         if ((i % 2) == 0) {
10587             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10588         } else {
10589             if (newblock) {
10590                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10591             } else {
10592                 numtext[0] = NULLCHAR;
10593             }
10594         }
10595         numlen = strlen(numtext);
10596         newblock = FALSE;
10597
10598         /* Print move number */
10599         blank = linelen > 0 && numlen > 0;
10600         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10601             fprintf(f, "\n");
10602             linelen = 0;
10603             blank = 0;
10604         }
10605         if (blank) {
10606             fprintf(f, " ");
10607             linelen++;
10608         }
10609         fprintf(f, "%s", numtext);
10610         linelen += numlen;
10611
10612         /* Get move */
10613         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10614         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10615
10616         /* Print move */
10617         blank = linelen > 0 && movelen > 0;
10618         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10619             fprintf(f, "\n");
10620             linelen = 0;
10621             blank = 0;
10622         }
10623         if (blank) {
10624             fprintf(f, " ");
10625             linelen++;
10626         }
10627         fprintf(f, "%s", move_buffer);
10628         linelen += movelen;
10629
10630         /* [AS] Add PV info if present */
10631         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10632             /* [HGM] add time */
10633             char buf[MSG_SIZ]; int seconds;
10634
10635             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10636
10637             if( seconds <= 0) buf[0] = 0; else
10638             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10639                 seconds = (seconds + 4)/10; // round to full seconds
10640                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10641                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10642             }
10643
10644             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10645                 pvInfoList[i].score >= 0 ? "+" : "",
10646                 pvInfoList[i].score / 100.0,
10647                 pvInfoList[i].depth,
10648                 buf );
10649
10650             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10651
10652             /* Print score/depth */
10653             blank = linelen > 0 && movelen > 0;
10654             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10655                 fprintf(f, "\n");
10656                 linelen = 0;
10657                 blank = 0;
10658             }
10659             if (blank) {
10660                 fprintf(f, " ");
10661                 linelen++;
10662             }
10663             fprintf(f, "%s", move_buffer);
10664             linelen += movelen;
10665         }
10666
10667         i++;
10668     }
10669     
10670     /* Start a new line */
10671     if (linelen > 0) fprintf(f, "\n");
10672
10673     /* Print comments after last move */
10674     if (commentList[i] != NULL) {
10675         fprintf(f, "%s\n", commentList[i]);
10676     }
10677
10678     /* Print result */
10679     if (gameInfo.resultDetails != NULL &&
10680         gameInfo.resultDetails[0] != NULLCHAR) {
10681         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10682                 PGNResult(gameInfo.result));
10683     } else {
10684         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10685     }
10686
10687     fclose(f);
10688     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10689     return TRUE;
10690 }
10691
10692 /* Save game in old style and close the file */
10693 int
10694 SaveGameOldStyle(f)
10695      FILE *f;
10696 {
10697     int i, offset;
10698     time_t tm;
10699     
10700     tm = time((time_t *) NULL);
10701     
10702     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10703     PrintOpponents(f);
10704     
10705     if (backwardMostMove > 0 || startedFromSetupPosition) {
10706         fprintf(f, "\n[--------------\n");
10707         PrintPosition(f, backwardMostMove);
10708         fprintf(f, "--------------]\n");
10709     } else {
10710         fprintf(f, "\n");
10711     }
10712
10713     i = backwardMostMove;
10714     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10715
10716     while (i < forwardMostMove) {
10717         if (commentList[i] != NULL) {
10718             fprintf(f, "[%s]\n", commentList[i]);
10719         }
10720
10721         if ((i % 2) == 1) {
10722             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10723             i++;
10724         } else {
10725             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10726             i++;
10727             if (commentList[i] != NULL) {
10728                 fprintf(f, "\n");
10729                 continue;
10730             }
10731             if (i >= forwardMostMove) {
10732                 fprintf(f, "\n");
10733                 break;
10734             }
10735             fprintf(f, "%s\n", parseList[i]);
10736             i++;
10737         }
10738     }
10739     
10740     if (commentList[i] != NULL) {
10741         fprintf(f, "[%s]\n", commentList[i]);
10742     }
10743
10744     /* This isn't really the old style, but it's close enough */
10745     if (gameInfo.resultDetails != NULL &&
10746         gameInfo.resultDetails[0] != NULLCHAR) {
10747         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10748                 gameInfo.resultDetails);
10749     } else {
10750         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10751     }
10752
10753     fclose(f);
10754     return TRUE;
10755 }
10756
10757 /* Save the current game to open file f and close the file */
10758 int
10759 SaveGame(f, dummy, dummy2)
10760      FILE *f;
10761      int dummy;
10762      char *dummy2;
10763 {
10764     if (gameMode == EditPosition) EditPositionDone(TRUE);
10765     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10766     if (appData.oldSaveStyle)
10767       return SaveGameOldStyle(f);
10768     else
10769       return SaveGamePGN(f);
10770 }
10771
10772 /* Save the current position to the given file */
10773 int
10774 SavePositionToFile(filename)
10775      char *filename;
10776 {
10777     FILE *f;
10778     char buf[MSG_SIZ];
10779
10780     if (strcmp(filename, "-") == 0) {
10781         return SavePosition(stdout, 0, NULL);
10782     } else {
10783         f = fopen(filename, "a");
10784         if (f == NULL) {
10785             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10786             DisplayError(buf, errno);
10787             return FALSE;
10788         } else {
10789             SavePosition(f, 0, NULL);
10790             return TRUE;
10791         }
10792     }
10793 }
10794
10795 /* Save the current position to the given open file and close the file */
10796 int
10797 SavePosition(f, dummy, dummy2)
10798      FILE *f;
10799      int dummy;
10800      char *dummy2;
10801 {
10802     time_t tm;
10803     char *fen;
10804     
10805     if (gameMode == EditPosition) EditPositionDone(TRUE);
10806     if (appData.oldSaveStyle) {
10807         tm = time((time_t *) NULL);
10808     
10809         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10810         PrintOpponents(f);
10811         fprintf(f, "[--------------\n");
10812         PrintPosition(f, currentMove);
10813         fprintf(f, "--------------]\n");
10814     } else {
10815         fen = PositionToFEN(currentMove, NULL);
10816         fprintf(f, "%s\n", fen);
10817         free(fen);
10818     }
10819     fclose(f);
10820     return TRUE;
10821 }
10822
10823 void
10824 ReloadCmailMsgEvent(unregister)
10825      int unregister;
10826 {
10827 #if !WIN32
10828     static char *inFilename = NULL;
10829     static char *outFilename;
10830     int i;
10831     struct stat inbuf, outbuf;
10832     int status;
10833     
10834     /* Any registered moves are unregistered if unregister is set, */
10835     /* i.e. invoked by the signal handler */
10836     if (unregister) {
10837         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10838             cmailMoveRegistered[i] = FALSE;
10839             if (cmailCommentList[i] != NULL) {
10840                 free(cmailCommentList[i]);
10841                 cmailCommentList[i] = NULL;
10842             }
10843         }
10844         nCmailMovesRegistered = 0;
10845     }
10846
10847     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10848         cmailResult[i] = CMAIL_NOT_RESULT;
10849     }
10850     nCmailResults = 0;
10851
10852     if (inFilename == NULL) {
10853         /* Because the filenames are static they only get malloced once  */
10854         /* and they never get freed                                      */
10855         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10856         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10857
10858         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10859         sprintf(outFilename, "%s.out", appData.cmailGameName);
10860     }
10861     
10862     status = stat(outFilename, &outbuf);
10863     if (status < 0) {
10864         cmailMailedMove = FALSE;
10865     } else {
10866         status = stat(inFilename, &inbuf);
10867         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10868     }
10869     
10870     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10871        counts the games, notes how each one terminated, etc.
10872        
10873        It would be nice to remove this kludge and instead gather all
10874        the information while building the game list.  (And to keep it
10875        in the game list nodes instead of having a bunch of fixed-size
10876        parallel arrays.)  Note this will require getting each game's
10877        termination from the PGN tags, as the game list builder does
10878        not process the game moves.  --mann
10879        */
10880     cmailMsgLoaded = TRUE;
10881     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10882     
10883     /* Load first game in the file or popup game menu */
10884     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10885
10886 #endif /* !WIN32 */
10887     return;
10888 }
10889
10890 int
10891 RegisterMove()
10892 {
10893     FILE *f;
10894     char string[MSG_SIZ];
10895
10896     if (   cmailMailedMove
10897         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10898         return TRUE;            /* Allow free viewing  */
10899     }
10900
10901     /* Unregister move to ensure that we don't leave RegisterMove        */
10902     /* with the move registered when the conditions for registering no   */
10903     /* longer hold                                                       */
10904     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10905         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10906         nCmailMovesRegistered --;
10907
10908         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10909           {
10910               free(cmailCommentList[lastLoadGameNumber - 1]);
10911               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10912           }
10913     }
10914
10915     if (cmailOldMove == -1) {
10916         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10917         return FALSE;
10918     }
10919
10920     if (currentMove > cmailOldMove + 1) {
10921         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10922         return FALSE;
10923     }
10924
10925     if (currentMove < cmailOldMove) {
10926         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10927         return FALSE;
10928     }
10929
10930     if (forwardMostMove > currentMove) {
10931         /* Silently truncate extra moves */
10932         TruncateGame();
10933     }
10934
10935     if (   (currentMove == cmailOldMove + 1)
10936         || (   (currentMove == cmailOldMove)
10937             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10938                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10939         if (gameInfo.result != GameUnfinished) {
10940             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10941         }
10942
10943         if (commentList[currentMove] != NULL) {
10944             cmailCommentList[lastLoadGameNumber - 1]
10945               = StrSave(commentList[currentMove]);
10946         }
10947         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10948
10949         if (appData.debugMode)
10950           fprintf(debugFP, "Saving %s for game %d\n",
10951                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10952
10953         sprintf(string,
10954                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10955         
10956         f = fopen(string, "w");
10957         if (appData.oldSaveStyle) {
10958             SaveGameOldStyle(f); /* also closes the file */
10959             
10960             sprintf(string, "%s.pos.out", appData.cmailGameName);
10961             f = fopen(string, "w");
10962             SavePosition(f, 0, NULL); /* also closes the file */
10963         } else {
10964             fprintf(f, "{--------------\n");
10965             PrintPosition(f, currentMove);
10966             fprintf(f, "--------------}\n\n");
10967             
10968             SaveGame(f, 0, NULL); /* also closes the file*/
10969         }
10970         
10971         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10972         nCmailMovesRegistered ++;
10973     } else if (nCmailGames == 1) {
10974         DisplayError(_("You have not made a move yet"), 0);
10975         return FALSE;
10976     }
10977
10978     return TRUE;
10979 }
10980
10981 void
10982 MailMoveEvent()
10983 {
10984 #if !WIN32
10985     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10986     FILE *commandOutput;
10987     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10988     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10989     int nBuffers;
10990     int i;
10991     int archived;
10992     char *arcDir;
10993
10994     if (! cmailMsgLoaded) {
10995         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10996         return;
10997     }
10998
10999     if (nCmailGames == nCmailResults) {
11000         DisplayError(_("No unfinished games"), 0);
11001         return;
11002     }
11003
11004 #if CMAIL_PROHIBIT_REMAIL
11005     if (cmailMailedMove) {
11006         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);
11007         DisplayError(msg, 0);
11008         return;
11009     }
11010 #endif
11011
11012     if (! (cmailMailedMove || RegisterMove())) return;
11013     
11014     if (   cmailMailedMove
11015         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11016         sprintf(string, partCommandString,
11017                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11018         commandOutput = popen(string, "r");
11019
11020         if (commandOutput == NULL) {
11021             DisplayError(_("Failed to invoke cmail"), 0);
11022         } else {
11023             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11024                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11025             }
11026             if (nBuffers > 1) {
11027                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11028                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11029                 nBytes = MSG_SIZ - 1;
11030             } else {
11031                 (void) memcpy(msg, buffer, nBytes);
11032             }
11033             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11034
11035             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11036                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11037
11038                 archived = TRUE;
11039                 for (i = 0; i < nCmailGames; i ++) {
11040                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11041                         archived = FALSE;
11042                     }
11043                 }
11044                 if (   archived
11045                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11046                         != NULL)) {
11047                     sprintf(buffer, "%s/%s.%s.archive",
11048                             arcDir,
11049                             appData.cmailGameName,
11050                             gameInfo.date);
11051                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11052                     cmailMsgLoaded = FALSE;
11053                 }
11054             }
11055
11056             DisplayInformation(msg);
11057             pclose(commandOutput);
11058         }
11059     } else {
11060         if ((*cmailMsg) != '\0') {
11061             DisplayInformation(cmailMsg);
11062         }
11063     }
11064
11065     return;
11066 #endif /* !WIN32 */
11067 }
11068
11069 char *
11070 CmailMsg()
11071 {
11072 #if WIN32
11073     return NULL;
11074 #else
11075     int  prependComma = 0;
11076     char number[5];
11077     char string[MSG_SIZ];       /* Space for game-list */
11078     int  i;
11079     
11080     if (!cmailMsgLoaded) return "";
11081
11082     if (cmailMailedMove) {
11083         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11084     } else {
11085         /* Create a list of games left */
11086         sprintf(string, "[");
11087         for (i = 0; i < nCmailGames; i ++) {
11088             if (! (   cmailMoveRegistered[i]
11089                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11090                 if (prependComma) {
11091                     sprintf(number, ",%d", i + 1);
11092                 } else {
11093                     sprintf(number, "%d", i + 1);
11094                     prependComma = 1;
11095                 }
11096                 
11097                 strcat(string, number);
11098             }
11099         }
11100         strcat(string, "]");
11101
11102         if (nCmailMovesRegistered + nCmailResults == 0) {
11103             switch (nCmailGames) {
11104               case 1:
11105                 sprintf(cmailMsg,
11106                         _("Still need to make move for game\n"));
11107                 break;
11108                 
11109               case 2:
11110                 sprintf(cmailMsg,
11111                         _("Still need to make moves for both games\n"));
11112                 break;
11113                 
11114               default:
11115                 sprintf(cmailMsg,
11116                         _("Still need to make moves for all %d games\n"),
11117                         nCmailGames);
11118                 break;
11119             }
11120         } else {
11121             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11122               case 1:
11123                 sprintf(cmailMsg,
11124                         _("Still need to make a move for game %s\n"),
11125                         string);
11126                 break;
11127                 
11128               case 0:
11129                 if (nCmailResults == nCmailGames) {
11130                     sprintf(cmailMsg, _("No unfinished games\n"));
11131                 } else {
11132                     sprintf(cmailMsg, _("Ready to send mail\n"));
11133                 }
11134                 break;
11135                 
11136               default:
11137                 sprintf(cmailMsg,
11138                         _("Still need to make moves for games %s\n"),
11139                         string);
11140             }
11141         }
11142     }
11143     return cmailMsg;
11144 #endif /* WIN32 */
11145 }
11146
11147 void
11148 ResetGameEvent()
11149 {
11150     if (gameMode == Training)
11151       SetTrainingModeOff();
11152
11153     Reset(TRUE, TRUE);
11154     cmailMsgLoaded = FALSE;
11155     if (appData.icsActive) {
11156       SendToICS(ics_prefix);
11157       SendToICS("refresh\n");
11158     }
11159 }
11160
11161 void
11162 ExitEvent(status)
11163      int status;
11164 {
11165     exiting++;
11166     if (exiting > 2) {
11167       /* Give up on clean exit */
11168       exit(status);
11169     }
11170     if (exiting > 1) {
11171       /* Keep trying for clean exit */
11172       return;
11173     }
11174
11175     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11176
11177     if (telnetISR != NULL) {
11178       RemoveInputSource(telnetISR);
11179     }
11180     if (icsPR != NoProc) {
11181       DestroyChildProcess(icsPR, TRUE);
11182     }
11183
11184     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11185     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11186
11187     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11188     /* make sure this other one finishes before killing it!                  */
11189     if(endingGame) { int count = 0;
11190         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11191         while(endingGame && count++ < 10) DoSleep(1);
11192         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11193     }
11194
11195     /* Kill off chess programs */
11196     if (first.pr != NoProc) {
11197         ExitAnalyzeMode();
11198         
11199         DoSleep( appData.delayBeforeQuit );
11200         SendToProgram("quit\n", &first);
11201         DoSleep( appData.delayAfterQuit );
11202         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11203     }
11204     if (second.pr != NoProc) {
11205         DoSleep( appData.delayBeforeQuit );
11206         SendToProgram("quit\n", &second);
11207         DoSleep( appData.delayAfterQuit );
11208         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11209     }
11210     if (first.isr != NULL) {
11211         RemoveInputSource(first.isr);
11212     }
11213     if (second.isr != NULL) {
11214         RemoveInputSource(second.isr);
11215     }
11216
11217     ShutDownFrontEnd();
11218     exit(status);
11219 }
11220
11221 void
11222 PauseEvent()
11223 {
11224     if (appData.debugMode)
11225         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11226     if (pausing) {
11227         pausing = FALSE;
11228         ModeHighlight();
11229         if (gameMode == MachinePlaysWhite ||
11230             gameMode == MachinePlaysBlack) {
11231             StartClocks();
11232         } else {
11233             DisplayBothClocks();
11234         }
11235         if (gameMode == PlayFromGameFile) {
11236             if (appData.timeDelay >= 0) 
11237                 AutoPlayGameLoop();
11238         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11239             Reset(FALSE, TRUE);
11240             SendToICS(ics_prefix);
11241             SendToICS("refresh\n");
11242         } else if (currentMove < forwardMostMove) {
11243             ForwardInner(forwardMostMove);
11244         }
11245         pauseExamInvalid = FALSE;
11246     } else {
11247         switch (gameMode) {
11248           default:
11249             return;
11250           case IcsExamining:
11251             pauseExamForwardMostMove = forwardMostMove;
11252             pauseExamInvalid = FALSE;
11253             /* fall through */
11254           case IcsObserving:
11255           case IcsPlayingWhite:
11256           case IcsPlayingBlack:
11257             pausing = TRUE;
11258             ModeHighlight();
11259             return;
11260           case PlayFromGameFile:
11261             (void) StopLoadGameTimer();
11262             pausing = TRUE;
11263             ModeHighlight();
11264             break;
11265           case BeginningOfGame:
11266             if (appData.icsActive) return;
11267             /* else fall through */
11268           case MachinePlaysWhite:
11269           case MachinePlaysBlack:
11270           case TwoMachinesPlay:
11271             if (forwardMostMove == 0)
11272               return;           /* don't pause if no one has moved */
11273             if ((gameMode == MachinePlaysWhite &&
11274                  !WhiteOnMove(forwardMostMove)) ||
11275                 (gameMode == MachinePlaysBlack &&
11276                  WhiteOnMove(forwardMostMove))) {
11277                 StopClocks();
11278             }
11279             pausing = TRUE;
11280             ModeHighlight();
11281             break;
11282         }
11283     }
11284 }
11285
11286 void
11287 EditCommentEvent()
11288 {
11289     char title[MSG_SIZ];
11290
11291     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11292         strcpy(title, _("Edit comment"));
11293     } else {
11294         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11295                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11296                 parseList[currentMove - 1]);
11297     }
11298
11299     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11300 }
11301
11302
11303 void
11304 EditTagsEvent()
11305 {
11306     char *tags = PGNTags(&gameInfo);
11307     EditTagsPopUp(tags);
11308     free(tags);
11309 }
11310
11311 void
11312 AnalyzeModeEvent()
11313 {
11314     if (appData.noChessProgram || gameMode == AnalyzeMode)
11315       return;
11316
11317     if (gameMode != AnalyzeFile) {
11318         if (!appData.icsEngineAnalyze) {
11319                EditGameEvent();
11320                if (gameMode != EditGame) return;
11321         }
11322         ResurrectChessProgram();
11323         SendToProgram("analyze\n", &first);
11324         first.analyzing = TRUE;
11325         /*first.maybeThinking = TRUE;*/
11326         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11327         EngineOutputPopUp();
11328     }
11329     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11330     pausing = FALSE;
11331     ModeHighlight();
11332     SetGameInfo();
11333
11334     StartAnalysisClock();
11335     GetTimeMark(&lastNodeCountTime);
11336     lastNodeCount = 0;
11337 }
11338
11339 void
11340 AnalyzeFileEvent()
11341 {
11342     if (appData.noChessProgram || gameMode == AnalyzeFile)
11343       return;
11344
11345     if (gameMode != AnalyzeMode) {
11346         EditGameEvent();
11347         if (gameMode != EditGame) return;
11348         ResurrectChessProgram();
11349         SendToProgram("analyze\n", &first);
11350         first.analyzing = TRUE;
11351         /*first.maybeThinking = TRUE;*/
11352         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11353         EngineOutputPopUp();
11354     }
11355     gameMode = AnalyzeFile;
11356     pausing = FALSE;
11357     ModeHighlight();
11358     SetGameInfo();
11359
11360     StartAnalysisClock();
11361     GetTimeMark(&lastNodeCountTime);
11362     lastNodeCount = 0;
11363 }
11364
11365 void
11366 MachineWhiteEvent()
11367 {
11368     char buf[MSG_SIZ];
11369     char *bookHit = NULL;
11370
11371     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11372       return;
11373
11374
11375     if (gameMode == PlayFromGameFile || 
11376         gameMode == TwoMachinesPlay  || 
11377         gameMode == Training         || 
11378         gameMode == AnalyzeMode      || 
11379         gameMode == EndOfGame)
11380         EditGameEvent();
11381
11382     if (gameMode == EditPosition) 
11383         EditPositionDone(TRUE);
11384
11385     if (!WhiteOnMove(currentMove)) {
11386         DisplayError(_("It is not White's turn"), 0);
11387         return;
11388     }
11389   
11390     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11391       ExitAnalyzeMode();
11392
11393     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11394         gameMode == AnalyzeFile)
11395         TruncateGame();
11396
11397     ResurrectChessProgram();    /* in case it isn't running */
11398     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11399         gameMode = MachinePlaysWhite;
11400         ResetClocks();
11401     } else
11402     gameMode = MachinePlaysWhite;
11403     pausing = FALSE;
11404     ModeHighlight();
11405     SetGameInfo();
11406     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11407     DisplayTitle(buf);
11408     if (first.sendName) {
11409       sprintf(buf, "name %s\n", gameInfo.black);
11410       SendToProgram(buf, &first);
11411     }
11412     if (first.sendTime) {
11413       if (first.useColors) {
11414         SendToProgram("black\n", &first); /*gnu kludge*/
11415       }
11416       SendTimeRemaining(&first, TRUE);
11417     }
11418     if (first.useColors) {
11419       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11420     }
11421     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11422     SetMachineThinkingEnables();
11423     first.maybeThinking = TRUE;
11424     StartClocks();
11425     firstMove = FALSE;
11426
11427     if (appData.autoFlipView && !flipView) {
11428       flipView = !flipView;
11429       DrawPosition(FALSE, NULL);
11430       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11431     }
11432
11433     if(bookHit) { // [HGM] book: simulate book reply
11434         static char bookMove[MSG_SIZ]; // a bit generous?
11435
11436         programStats.nodes = programStats.depth = programStats.time = 
11437         programStats.score = programStats.got_only_move = 0;
11438         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11439
11440         strcpy(bookMove, "move ");
11441         strcat(bookMove, bookHit);
11442         HandleMachineMove(bookMove, &first);
11443     }
11444 }
11445
11446 void
11447 MachineBlackEvent()
11448 {
11449     char buf[MSG_SIZ];
11450    char *bookHit = NULL;
11451
11452     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11453         return;
11454
11455
11456     if (gameMode == PlayFromGameFile || 
11457         gameMode == TwoMachinesPlay  || 
11458         gameMode == Training         || 
11459         gameMode == AnalyzeMode      || 
11460         gameMode == EndOfGame)
11461         EditGameEvent();
11462
11463     if (gameMode == EditPosition) 
11464         EditPositionDone(TRUE);
11465
11466     if (WhiteOnMove(currentMove)) {
11467         DisplayError(_("It is not Black's turn"), 0);
11468         return;
11469     }
11470     
11471     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11472       ExitAnalyzeMode();
11473
11474     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11475         gameMode == AnalyzeFile)
11476         TruncateGame();
11477
11478     ResurrectChessProgram();    /* in case it isn't running */
11479     gameMode = MachinePlaysBlack;
11480     pausing = FALSE;
11481     ModeHighlight();
11482     SetGameInfo();
11483     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11484     DisplayTitle(buf);
11485     if (first.sendName) {
11486       sprintf(buf, "name %s\n", gameInfo.white);
11487       SendToProgram(buf, &first);
11488     }
11489     if (first.sendTime) {
11490       if (first.useColors) {
11491         SendToProgram("white\n", &first); /*gnu kludge*/
11492       }
11493       SendTimeRemaining(&first, FALSE);
11494     }
11495     if (first.useColors) {
11496       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11497     }
11498     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11499     SetMachineThinkingEnables();
11500     first.maybeThinking = TRUE;
11501     StartClocks();
11502
11503     if (appData.autoFlipView && flipView) {
11504       flipView = !flipView;
11505       DrawPosition(FALSE, NULL);
11506       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11507     }
11508     if(bookHit) { // [HGM] book: simulate book reply
11509         static char bookMove[MSG_SIZ]; // a bit generous?
11510
11511         programStats.nodes = programStats.depth = programStats.time = 
11512         programStats.score = programStats.got_only_move = 0;
11513         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11514
11515         strcpy(bookMove, "move ");
11516         strcat(bookMove, bookHit);
11517         HandleMachineMove(bookMove, &first);
11518     }
11519 }
11520
11521
11522 void
11523 DisplayTwoMachinesTitle()
11524 {
11525     char buf[MSG_SIZ];
11526     if (appData.matchGames > 0) {
11527         if (first.twoMachinesColor[0] == 'w') {
11528             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11529                     gameInfo.white, gameInfo.black,
11530                     first.matchWins, second.matchWins,
11531                     matchGame - 1 - (first.matchWins + second.matchWins));
11532         } else {
11533             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11534                     gameInfo.white, gameInfo.black,
11535                     second.matchWins, first.matchWins,
11536                     matchGame - 1 - (first.matchWins + second.matchWins));
11537         }
11538     } else {
11539         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11540     }
11541     DisplayTitle(buf);
11542 }
11543
11544 void
11545 TwoMachinesEvent P((void))
11546 {
11547     int i;
11548     char buf[MSG_SIZ];
11549     ChessProgramState *onmove;
11550     char *bookHit = NULL;
11551     
11552     if (appData.noChessProgram) return;
11553
11554     switch (gameMode) {
11555       case TwoMachinesPlay:
11556         return;
11557       case MachinePlaysWhite:
11558       case MachinePlaysBlack:
11559         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11560             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11561             return;
11562         }
11563         /* fall through */
11564       case BeginningOfGame:
11565       case PlayFromGameFile:
11566       case EndOfGame:
11567         EditGameEvent();
11568         if (gameMode != EditGame) return;
11569         break;
11570       case EditPosition:
11571         EditPositionDone(TRUE);
11572         break;
11573       case AnalyzeMode:
11574       case AnalyzeFile:
11575         ExitAnalyzeMode();
11576         break;
11577       case EditGame:
11578       default:
11579         break;
11580     }
11581
11582 //    forwardMostMove = currentMove;
11583     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11584     ResurrectChessProgram();    /* in case first program isn't running */
11585
11586     if (second.pr == NULL) {
11587         StartChessProgram(&second);
11588         if (second.protocolVersion == 1) {
11589           TwoMachinesEventIfReady();
11590         } else {
11591           /* kludge: allow timeout for initial "feature" command */
11592           FreezeUI();
11593           DisplayMessage("", _("Starting second chess program"));
11594           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11595         }
11596         return;
11597     }
11598     DisplayMessage("", "");
11599     InitChessProgram(&second, FALSE);
11600     SendToProgram("force\n", &second);
11601     if (startedFromSetupPosition) {
11602         SendBoard(&second, backwardMostMove);
11603     if (appData.debugMode) {
11604         fprintf(debugFP, "Two Machines\n");
11605     }
11606     }
11607     for (i = backwardMostMove; i < forwardMostMove; i++) {
11608         SendMoveToProgram(i, &second);
11609     }
11610
11611     gameMode = TwoMachinesPlay;
11612     pausing = FALSE;
11613     ModeHighlight();
11614     SetGameInfo();
11615     DisplayTwoMachinesTitle();
11616     firstMove = TRUE;
11617     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11618         onmove = &first;
11619     } else {
11620         onmove = &second;
11621     }
11622
11623     SendToProgram(first.computerString, &first);
11624     if (first.sendName) {
11625       sprintf(buf, "name %s\n", second.tidy);
11626       SendToProgram(buf, &first);
11627     }
11628     SendToProgram(second.computerString, &second);
11629     if (second.sendName) {
11630       sprintf(buf, "name %s\n", first.tidy);
11631       SendToProgram(buf, &second);
11632     }
11633
11634     ResetClocks();
11635     if (!first.sendTime || !second.sendTime) {
11636         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11637         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11638     }
11639     if (onmove->sendTime) {
11640       if (onmove->useColors) {
11641         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11642       }
11643       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11644     }
11645     if (onmove->useColors) {
11646       SendToProgram(onmove->twoMachinesColor, onmove);
11647     }
11648     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11649 //    SendToProgram("go\n", onmove);
11650     onmove->maybeThinking = TRUE;
11651     SetMachineThinkingEnables();
11652
11653     StartClocks();
11654
11655     if(bookHit) { // [HGM] book: simulate book reply
11656         static char bookMove[MSG_SIZ]; // a bit generous?
11657
11658         programStats.nodes = programStats.depth = programStats.time = 
11659         programStats.score = programStats.got_only_move = 0;
11660         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11661
11662         strcpy(bookMove, "move ");
11663         strcat(bookMove, bookHit);
11664         savedMessage = bookMove; // args for deferred call
11665         savedState = onmove;
11666         ScheduleDelayedEvent(DeferredBookMove, 1);
11667     }
11668 }
11669
11670 void
11671 TrainingEvent()
11672 {
11673     if (gameMode == Training) {
11674       SetTrainingModeOff();
11675       gameMode = PlayFromGameFile;
11676       DisplayMessage("", _("Training mode off"));
11677     } else {
11678       gameMode = Training;
11679       animateTraining = appData.animate;
11680
11681       /* make sure we are not already at the end of the game */
11682       if (currentMove < forwardMostMove) {
11683         SetTrainingModeOn();
11684         DisplayMessage("", _("Training mode on"));
11685       } else {
11686         gameMode = PlayFromGameFile;
11687         DisplayError(_("Already at end of game"), 0);
11688       }
11689     }
11690     ModeHighlight();
11691 }
11692
11693 void
11694 IcsClientEvent()
11695 {
11696     if (!appData.icsActive) return;
11697     switch (gameMode) {
11698       case IcsPlayingWhite:
11699       case IcsPlayingBlack:
11700       case IcsObserving:
11701       case IcsIdle:
11702       case BeginningOfGame:
11703       case IcsExamining:
11704         return;
11705
11706       case EditGame:
11707         break;
11708
11709       case EditPosition:
11710         EditPositionDone(TRUE);
11711         break;
11712
11713       case AnalyzeMode:
11714       case AnalyzeFile:
11715         ExitAnalyzeMode();
11716         break;
11717         
11718       default:
11719         EditGameEvent();
11720         break;
11721     }
11722
11723     gameMode = IcsIdle;
11724     ModeHighlight();
11725     return;
11726 }
11727
11728
11729 void
11730 EditGameEvent()
11731 {
11732     int i;
11733
11734     switch (gameMode) {
11735       case Training:
11736         SetTrainingModeOff();
11737         break;
11738       case MachinePlaysWhite:
11739       case MachinePlaysBlack:
11740       case BeginningOfGame:
11741         SendToProgram("force\n", &first);
11742         SetUserThinkingEnables();
11743         break;
11744       case PlayFromGameFile:
11745         (void) StopLoadGameTimer();
11746         if (gameFileFP != NULL) {
11747             gameFileFP = NULL;
11748         }
11749         break;
11750       case EditPosition:
11751         EditPositionDone(TRUE);
11752         break;
11753       case AnalyzeMode:
11754       case AnalyzeFile:
11755         ExitAnalyzeMode();
11756         SendToProgram("force\n", &first);
11757         break;
11758       case TwoMachinesPlay:
11759         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11760         ResurrectChessProgram();
11761         SetUserThinkingEnables();
11762         break;
11763       case EndOfGame:
11764         ResurrectChessProgram();
11765         break;
11766       case IcsPlayingBlack:
11767       case IcsPlayingWhite:
11768         DisplayError(_("Warning: You are still playing a game"), 0);
11769         break;
11770       case IcsObserving:
11771         DisplayError(_("Warning: You are still observing a game"), 0);
11772         break;
11773       case IcsExamining:
11774         DisplayError(_("Warning: You are still examining a game"), 0);
11775         break;
11776       case IcsIdle:
11777         break;
11778       case EditGame:
11779       default:
11780         return;
11781     }
11782     
11783     pausing = FALSE;
11784     StopClocks();
11785     first.offeredDraw = second.offeredDraw = 0;
11786
11787     if (gameMode == PlayFromGameFile) {
11788         whiteTimeRemaining = timeRemaining[0][currentMove];
11789         blackTimeRemaining = timeRemaining[1][currentMove];
11790         DisplayTitle("");
11791     }
11792
11793     if (gameMode == MachinePlaysWhite ||
11794         gameMode == MachinePlaysBlack ||
11795         gameMode == TwoMachinesPlay ||
11796         gameMode == EndOfGame) {
11797         i = forwardMostMove;
11798         while (i > currentMove) {
11799             SendToProgram("undo\n", &first);
11800             i--;
11801         }
11802         whiteTimeRemaining = timeRemaining[0][currentMove];
11803         blackTimeRemaining = timeRemaining[1][currentMove];
11804         DisplayBothClocks();
11805         if (whiteFlag || blackFlag) {
11806             whiteFlag = blackFlag = 0;
11807         }
11808         DisplayTitle("");
11809     }           
11810     
11811     gameMode = EditGame;
11812     ModeHighlight();
11813     SetGameInfo();
11814 }
11815
11816
11817 void
11818 EditPositionEvent()
11819 {
11820     if (gameMode == EditPosition) {
11821         EditGameEvent();
11822         return;
11823     }
11824     
11825     EditGameEvent();
11826     if (gameMode != EditGame) return;
11827     
11828     gameMode = EditPosition;
11829     ModeHighlight();
11830     SetGameInfo();
11831     if (currentMove > 0)
11832       CopyBoard(boards[0], boards[currentMove]);
11833     
11834     blackPlaysFirst = !WhiteOnMove(currentMove);
11835     ResetClocks();
11836     currentMove = forwardMostMove = backwardMostMove = 0;
11837     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11838     DisplayMove(-1);
11839 }
11840
11841 void
11842 ExitAnalyzeMode()
11843 {
11844     /* [DM] icsEngineAnalyze - possible call from other functions */
11845     if (appData.icsEngineAnalyze) {
11846         appData.icsEngineAnalyze = FALSE;
11847
11848         DisplayMessage("",_("Close ICS engine analyze..."));
11849     }
11850     if (first.analysisSupport && first.analyzing) {
11851       SendToProgram("exit\n", &first);
11852       first.analyzing = FALSE;
11853     }
11854     thinkOutput[0] = NULLCHAR;
11855 }
11856
11857 void
11858 EditPositionDone(Boolean fakeRights)
11859 {
11860     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11861
11862     startedFromSetupPosition = TRUE;
11863     InitChessProgram(&first, FALSE);
11864     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11865       boards[0][EP_STATUS] = EP_NONE;
11866       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11867     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11868         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11869         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11870       } else boards[0][CASTLING][2] = NoRights;
11871     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11872         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11873         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11874       } else boards[0][CASTLING][5] = NoRights;
11875     }
11876     SendToProgram("force\n", &first);
11877     if (blackPlaysFirst) {
11878         strcpy(moveList[0], "");
11879         strcpy(parseList[0], "");
11880         currentMove = forwardMostMove = backwardMostMove = 1;
11881         CopyBoard(boards[1], boards[0]);
11882     } else {
11883         currentMove = forwardMostMove = backwardMostMove = 0;
11884     }
11885     SendBoard(&first, forwardMostMove);
11886     if (appData.debugMode) {
11887         fprintf(debugFP, "EditPosDone\n");
11888     }
11889     DisplayTitle("");
11890     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11891     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11892     gameMode = EditGame;
11893     ModeHighlight();
11894     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11895     ClearHighlights(); /* [AS] */
11896 }
11897
11898 /* Pause for `ms' milliseconds */
11899 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11900 void
11901 TimeDelay(ms)
11902      long ms;
11903 {
11904     TimeMark m1, m2;
11905
11906     GetTimeMark(&m1);
11907     do {
11908         GetTimeMark(&m2);
11909     } while (SubtractTimeMarks(&m2, &m1) < ms);
11910 }
11911
11912 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11913 void
11914 SendMultiLineToICS(buf)
11915      char *buf;
11916 {
11917     char temp[MSG_SIZ+1], *p;
11918     int len;
11919
11920     len = strlen(buf);
11921     if (len > MSG_SIZ)
11922       len = MSG_SIZ;
11923   
11924     strncpy(temp, buf, len);
11925     temp[len] = 0;
11926
11927     p = temp;
11928     while (*p) {
11929         if (*p == '\n' || *p == '\r')
11930           *p = ' ';
11931         ++p;
11932     }
11933
11934     strcat(temp, "\n");
11935     SendToICS(temp);
11936     SendToPlayer(temp, strlen(temp));
11937 }
11938
11939 void
11940 SetWhiteToPlayEvent()
11941 {
11942     if (gameMode == EditPosition) {
11943         blackPlaysFirst = FALSE;
11944         DisplayBothClocks();    /* works because currentMove is 0 */
11945     } else if (gameMode == IcsExamining) {
11946         SendToICS(ics_prefix);
11947         SendToICS("tomove white\n");
11948     }
11949 }
11950
11951 void
11952 SetBlackToPlayEvent()
11953 {
11954     if (gameMode == EditPosition) {
11955         blackPlaysFirst = TRUE;
11956         currentMove = 1;        /* kludge */
11957         DisplayBothClocks();
11958         currentMove = 0;
11959     } else if (gameMode == IcsExamining) {
11960         SendToICS(ics_prefix);
11961         SendToICS("tomove black\n");
11962     }
11963 }
11964
11965 void
11966 EditPositionMenuEvent(selection, x, y)
11967      ChessSquare selection;
11968      int x, y;
11969 {
11970     char buf[MSG_SIZ];
11971     ChessSquare piece = boards[0][y][x];
11972
11973     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11974
11975     switch (selection) {
11976       case ClearBoard:
11977         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11978             SendToICS(ics_prefix);
11979             SendToICS("bsetup clear\n");
11980         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11981             SendToICS(ics_prefix);
11982             SendToICS("clearboard\n");
11983         } else {
11984             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11985                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11986                 for (y = 0; y < BOARD_HEIGHT; y++) {
11987                     if (gameMode == IcsExamining) {
11988                         if (boards[currentMove][y][x] != EmptySquare) {
11989                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11990                                     AAA + x, ONE + y);
11991                             SendToICS(buf);
11992                         }
11993                     } else {
11994                         boards[0][y][x] = p;
11995                     }
11996                 }
11997             }
11998         }
11999         if (gameMode == EditPosition) {
12000             DrawPosition(FALSE, boards[0]);
12001         }
12002         break;
12003
12004       case WhitePlay:
12005         SetWhiteToPlayEvent();
12006         break;
12007
12008       case BlackPlay:
12009         SetBlackToPlayEvent();
12010         break;
12011
12012       case EmptySquare:
12013         if (gameMode == IcsExamining) {
12014             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12015             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12016             SendToICS(buf);
12017         } else {
12018             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12019                 if(x == BOARD_LEFT-2) {
12020                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12021                     boards[0][y][1] = 0;
12022                 } else
12023                 if(x == BOARD_RGHT+1) {
12024                     if(y >= gameInfo.holdingsSize) break;
12025                     boards[0][y][BOARD_WIDTH-2] = 0;
12026                 } else break;
12027             }
12028             boards[0][y][x] = EmptySquare;
12029             DrawPosition(FALSE, boards[0]);
12030         }
12031         break;
12032
12033       case PromotePiece:
12034         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12035            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12036             selection = (ChessSquare) (PROMOTED piece);
12037         } else if(piece == EmptySquare) selection = WhiteSilver;
12038         else selection = (ChessSquare)((int)piece - 1);
12039         goto defaultlabel;
12040
12041       case DemotePiece:
12042         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12043            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12044             selection = (ChessSquare) (DEMOTED piece);
12045         } else if(piece == EmptySquare) selection = BlackSilver;
12046         else selection = (ChessSquare)((int)piece + 1);       
12047         goto defaultlabel;
12048
12049       case WhiteQueen:
12050       case BlackQueen:
12051         if(gameInfo.variant == VariantShatranj ||
12052            gameInfo.variant == VariantXiangqi  ||
12053            gameInfo.variant == VariantCourier  ||
12054            gameInfo.variant == VariantMakruk     )
12055             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12056         goto defaultlabel;
12057
12058       case WhiteKing:
12059       case BlackKing:
12060         if(gameInfo.variant == VariantXiangqi)
12061             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12062         if(gameInfo.variant == VariantKnightmate)
12063             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12064       default:
12065         defaultlabel:
12066         if (gameMode == IcsExamining) {
12067             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12068             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12069                     PieceToChar(selection), AAA + x, ONE + y);
12070             SendToICS(buf);
12071         } else {
12072             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12073                 int n;
12074                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12075                     n = PieceToNumber(selection - BlackPawn);
12076                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12077                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12078                     boards[0][BOARD_HEIGHT-1-n][1]++;
12079                 } else
12080                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12081                     n = PieceToNumber(selection);
12082                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12083                     boards[0][n][BOARD_WIDTH-1] = selection;
12084                     boards[0][n][BOARD_WIDTH-2]++;
12085                 }
12086             } else
12087             boards[0][y][x] = selection;
12088             DrawPosition(TRUE, boards[0]);
12089         }
12090         break;
12091     }
12092 }
12093
12094
12095 void
12096 DropMenuEvent(selection, x, y)
12097      ChessSquare selection;
12098      int x, y;
12099 {
12100     ChessMove moveType;
12101
12102     switch (gameMode) {
12103       case IcsPlayingWhite:
12104       case MachinePlaysBlack:
12105         if (!WhiteOnMove(currentMove)) {
12106             DisplayMoveError(_("It is Black's turn"));
12107             return;
12108         }
12109         moveType = WhiteDrop;
12110         break;
12111       case IcsPlayingBlack:
12112       case MachinePlaysWhite:
12113         if (WhiteOnMove(currentMove)) {
12114             DisplayMoveError(_("It is White's turn"));
12115             return;
12116         }
12117         moveType = BlackDrop;
12118         break;
12119       case EditGame:
12120         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12121         break;
12122       default:
12123         return;
12124     }
12125
12126     if (moveType == BlackDrop && selection < BlackPawn) {
12127       selection = (ChessSquare) ((int) selection
12128                                  + (int) BlackPawn - (int) WhitePawn);
12129     }
12130     if (boards[currentMove][y][x] != EmptySquare) {
12131         DisplayMoveError(_("That square is occupied"));
12132         return;
12133     }
12134
12135     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12136 }
12137
12138 void
12139 AcceptEvent()
12140 {
12141     /* Accept a pending offer of any kind from opponent */
12142     
12143     if (appData.icsActive) {
12144         SendToICS(ics_prefix);
12145         SendToICS("accept\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             TruncateGame();
12152             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12153             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12154         } else {
12155             DisplayError(_("There is no pending offer on this move"), 0);
12156             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12157         }
12158     } else {
12159         /* Not used for offers from chess program */
12160     }
12161 }
12162
12163 void
12164 DeclineEvent()
12165 {
12166     /* Decline a pending offer of any kind from opponent */
12167     
12168     if (appData.icsActive) {
12169         SendToICS(ics_prefix);
12170         SendToICS("decline\n");
12171     } else if (cmailMsgLoaded) {
12172         if (currentMove == cmailOldMove &&
12173             commentList[cmailOldMove] != NULL &&
12174             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12175                    "Black offers a draw" : "White offers a draw")) {
12176 #ifdef NOTDEF
12177             AppendComment(cmailOldMove, "Draw declined", TRUE);
12178             DisplayComment(cmailOldMove - 1, "Draw declined");
12179 #endif /*NOTDEF*/
12180         } else {
12181             DisplayError(_("There is no pending offer on this move"), 0);
12182         }
12183     } else {
12184         /* Not used for offers from chess program */
12185     }
12186 }
12187
12188 void
12189 RematchEvent()
12190 {
12191     /* Issue ICS rematch command */
12192     if (appData.icsActive) {
12193         SendToICS(ics_prefix);
12194         SendToICS("rematch\n");
12195     }
12196 }
12197
12198 void
12199 CallFlagEvent()
12200 {
12201     /* Call your opponent's flag (claim a win on time) */
12202     if (appData.icsActive) {
12203         SendToICS(ics_prefix);
12204         SendToICS("flag\n");
12205     } else {
12206         switch (gameMode) {
12207           default:
12208             return;
12209           case MachinePlaysWhite:
12210             if (whiteFlag) {
12211                 if (blackFlag)
12212                   GameEnds(GameIsDrawn, "Both players ran out of time",
12213                            GE_PLAYER);
12214                 else
12215                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12216             } else {
12217                 DisplayError(_("Your opponent is not out of time"), 0);
12218             }
12219             break;
12220           case MachinePlaysBlack:
12221             if (blackFlag) {
12222                 if (whiteFlag)
12223                   GameEnds(GameIsDrawn, "Both players ran out of time",
12224                            GE_PLAYER);
12225                 else
12226                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12227             } else {
12228                 DisplayError(_("Your opponent is not out of time"), 0);
12229             }
12230             break;
12231         }
12232     }
12233 }
12234
12235 void
12236 DrawEvent()
12237 {
12238     /* Offer draw or accept pending draw offer from opponent */
12239     
12240     if (appData.icsActive) {
12241         /* Note: tournament rules require draw offers to be
12242            made after you make your move but before you punch
12243            your clock.  Currently ICS doesn't let you do that;
12244            instead, you immediately punch your clock after making
12245            a move, but you can offer a draw at any time. */
12246         
12247         SendToICS(ics_prefix);
12248         SendToICS("draw\n");
12249         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12250     } else if (cmailMsgLoaded) {
12251         if (currentMove == cmailOldMove &&
12252             commentList[cmailOldMove] != NULL &&
12253             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12254                    "Black offers a draw" : "White offers a draw")) {
12255             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12256             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12257         } else if (currentMove == cmailOldMove + 1) {
12258             char *offer = WhiteOnMove(cmailOldMove) ?
12259               "White offers a draw" : "Black offers a draw";
12260             AppendComment(currentMove, offer, TRUE);
12261             DisplayComment(currentMove - 1, offer);
12262             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12263         } else {
12264             DisplayError(_("You must make your move before offering a draw"), 0);
12265             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12266         }
12267     } else if (first.offeredDraw) {
12268         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12269     } else {
12270         if (first.sendDrawOffers) {
12271             SendToProgram("draw\n", &first);
12272             userOfferedDraw = TRUE;
12273         }
12274     }
12275 }
12276
12277 void
12278 AdjournEvent()
12279 {
12280     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12281     
12282     if (appData.icsActive) {
12283         SendToICS(ics_prefix);
12284         SendToICS("adjourn\n");
12285     } else {
12286         /* Currently GNU Chess doesn't offer or accept Adjourns */
12287     }
12288 }
12289
12290
12291 void
12292 AbortEvent()
12293 {
12294     /* Offer Abort or accept pending Abort offer from opponent */
12295     
12296     if (appData.icsActive) {
12297         SendToICS(ics_prefix);
12298         SendToICS("abort\n");
12299     } else {
12300         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12301     }
12302 }
12303
12304 void
12305 ResignEvent()
12306 {
12307     /* Resign.  You can do this even if it's not your turn. */
12308     
12309     if (appData.icsActive) {
12310         SendToICS(ics_prefix);
12311         SendToICS("resign\n");
12312     } else {
12313         switch (gameMode) {
12314           case MachinePlaysWhite:
12315             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12316             break;
12317           case MachinePlaysBlack:
12318             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12319             break;
12320           case EditGame:
12321             if (cmailMsgLoaded) {
12322                 TruncateGame();
12323                 if (WhiteOnMove(cmailOldMove)) {
12324                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12325                 } else {
12326                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12327                 }
12328                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12329             }
12330             break;
12331           default:
12332             break;
12333         }
12334     }
12335 }
12336
12337
12338 void
12339 StopObservingEvent()
12340 {
12341     /* Stop observing current games */
12342     SendToICS(ics_prefix);
12343     SendToICS("unobserve\n");
12344 }
12345
12346 void
12347 StopExaminingEvent()
12348 {
12349     /* Stop observing current game */
12350     SendToICS(ics_prefix);
12351     SendToICS("unexamine\n");
12352 }
12353
12354 void
12355 ForwardInner(target)
12356      int target;
12357 {
12358     int limit;
12359
12360     if (appData.debugMode)
12361         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12362                 target, currentMove, forwardMostMove);
12363
12364     if (gameMode == EditPosition)
12365       return;
12366
12367     if (gameMode == PlayFromGameFile && !pausing)
12368       PauseEvent();
12369     
12370     if (gameMode == IcsExamining && pausing)
12371       limit = pauseExamForwardMostMove;
12372     else
12373       limit = forwardMostMove;
12374     
12375     if (target > limit) target = limit;
12376
12377     if (target > 0 && moveList[target - 1][0]) {
12378         int fromX, fromY, toX, toY;
12379         toX = moveList[target - 1][2] - AAA;
12380         toY = moveList[target - 1][3] - ONE;
12381         if (moveList[target - 1][1] == '@') {
12382             if (appData.highlightLastMove) {
12383                 SetHighlights(-1, -1, toX, toY);
12384             }
12385         } else {
12386             fromX = moveList[target - 1][0] - AAA;
12387             fromY = moveList[target - 1][1] - ONE;
12388             if (target == currentMove + 1) {
12389                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12390             }
12391             if (appData.highlightLastMove) {
12392                 SetHighlights(fromX, fromY, toX, toY);
12393             }
12394         }
12395     }
12396     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12397         gameMode == Training || gameMode == PlayFromGameFile || 
12398         gameMode == AnalyzeFile) {
12399         while (currentMove < target) {
12400             SendMoveToProgram(currentMove++, &first);
12401         }
12402     } else {
12403         currentMove = target;
12404     }
12405     
12406     if (gameMode == EditGame || gameMode == EndOfGame) {
12407         whiteTimeRemaining = timeRemaining[0][currentMove];
12408         blackTimeRemaining = timeRemaining[1][currentMove];
12409     }
12410     DisplayBothClocks();
12411     DisplayMove(currentMove - 1);
12412     DrawPosition(FALSE, boards[currentMove]);
12413     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12414     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12415         DisplayComment(currentMove - 1, commentList[currentMove]);
12416     }
12417 }
12418
12419
12420 void
12421 ForwardEvent()
12422 {
12423     if (gameMode == IcsExamining && !pausing) {
12424         SendToICS(ics_prefix);
12425         SendToICS("forward\n");
12426     } else {
12427         ForwardInner(currentMove + 1);
12428     }
12429 }
12430
12431 void
12432 ToEndEvent()
12433 {
12434     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12435         /* to optimze, we temporarily turn off analysis mode while we feed
12436          * the remaining moves to the engine. Otherwise we get analysis output
12437          * after each move.
12438          */ 
12439         if (first.analysisSupport) {
12440           SendToProgram("exit\nforce\n", &first);
12441           first.analyzing = FALSE;
12442         }
12443     }
12444         
12445     if (gameMode == IcsExamining && !pausing) {
12446         SendToICS(ics_prefix);
12447         SendToICS("forward 999999\n");
12448     } else {
12449         ForwardInner(forwardMostMove);
12450     }
12451
12452     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12453         /* we have fed all the moves, so reactivate analysis mode */
12454         SendToProgram("analyze\n", &first);
12455         first.analyzing = TRUE;
12456         /*first.maybeThinking = TRUE;*/
12457         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12458     }
12459 }
12460
12461 void
12462 BackwardInner(target)
12463      int target;
12464 {
12465     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12466
12467     if (appData.debugMode)
12468         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12469                 target, currentMove, forwardMostMove);
12470
12471     if (gameMode == EditPosition) return;
12472     if (currentMove <= backwardMostMove) {
12473         ClearHighlights();
12474         DrawPosition(full_redraw, boards[currentMove]);
12475         return;
12476     }
12477     if (gameMode == PlayFromGameFile && !pausing)
12478       PauseEvent();
12479     
12480     if (moveList[target][0]) {
12481         int fromX, fromY, toX, toY;
12482         toX = moveList[target][2] - AAA;
12483         toY = moveList[target][3] - ONE;
12484         if (moveList[target][1] == '@') {
12485             if (appData.highlightLastMove) {
12486                 SetHighlights(-1, -1, toX, toY);
12487             }
12488         } else {
12489             fromX = moveList[target][0] - AAA;
12490             fromY = moveList[target][1] - ONE;
12491             if (target == currentMove - 1) {
12492                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12493             }
12494             if (appData.highlightLastMove) {
12495                 SetHighlights(fromX, fromY, toX, toY);
12496             }
12497         }
12498     }
12499     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12500         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12501         while (currentMove > target) {
12502             SendToProgram("undo\n", &first);
12503             currentMove--;
12504         }
12505     } else {
12506         currentMove = target;
12507     }
12508     
12509     if (gameMode == EditGame || gameMode == EndOfGame) {
12510         whiteTimeRemaining = timeRemaining[0][currentMove];
12511         blackTimeRemaining = timeRemaining[1][currentMove];
12512     }
12513     DisplayBothClocks();
12514     DisplayMove(currentMove - 1);
12515     DrawPosition(full_redraw, boards[currentMove]);
12516     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12517     // [HGM] PV info: routine tests if comment empty
12518     DisplayComment(currentMove - 1, commentList[currentMove]);
12519 }
12520
12521 void
12522 BackwardEvent()
12523 {
12524     if (gameMode == IcsExamining && !pausing) {
12525         SendToICS(ics_prefix);
12526         SendToICS("backward\n");
12527     } else {
12528         BackwardInner(currentMove - 1);
12529     }
12530 }
12531
12532 void
12533 ToStartEvent()
12534 {
12535     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12536         /* to optimize, we temporarily turn off analysis mode while we undo
12537          * all the moves. Otherwise we get analysis output after each undo.
12538          */ 
12539         if (first.analysisSupport) {
12540           SendToProgram("exit\nforce\n", &first);
12541           first.analyzing = FALSE;
12542         }
12543     }
12544
12545     if (gameMode == IcsExamining && !pausing) {
12546         SendToICS(ics_prefix);
12547         SendToICS("backward 999999\n");
12548     } else {
12549         BackwardInner(backwardMostMove);
12550     }
12551
12552     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12553         /* we have fed all the moves, so reactivate analysis mode */
12554         SendToProgram("analyze\n", &first);
12555         first.analyzing = TRUE;
12556         /*first.maybeThinking = TRUE;*/
12557         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12558     }
12559 }
12560
12561 void
12562 ToNrEvent(int to)
12563 {
12564   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12565   if (to >= forwardMostMove) to = forwardMostMove;
12566   if (to <= backwardMostMove) to = backwardMostMove;
12567   if (to < currentMove) {
12568     BackwardInner(to);
12569   } else {
12570     ForwardInner(to);
12571   }
12572 }
12573
12574 void
12575 RevertEvent()
12576 {
12577     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12578         return;
12579     }
12580     if (gameMode != IcsExamining) {
12581         DisplayError(_("You are not examining a game"), 0);
12582         return;
12583     }
12584     if (pausing) {
12585         DisplayError(_("You can't revert while pausing"), 0);
12586         return;
12587     }
12588     SendToICS(ics_prefix);
12589     SendToICS("revert\n");
12590 }
12591
12592 void
12593 RetractMoveEvent()
12594 {
12595     switch (gameMode) {
12596       case MachinePlaysWhite:
12597       case MachinePlaysBlack:
12598         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12599             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12600             return;
12601         }
12602         if (forwardMostMove < 2) return;
12603         currentMove = forwardMostMove = forwardMostMove - 2;
12604         whiteTimeRemaining = timeRemaining[0][currentMove];
12605         blackTimeRemaining = timeRemaining[1][currentMove];
12606         DisplayBothClocks();
12607         DisplayMove(currentMove - 1);
12608         ClearHighlights();/*!! could figure this out*/
12609         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12610         SendToProgram("remove\n", &first);
12611         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12612         break;
12613
12614       case BeginningOfGame:
12615       default:
12616         break;
12617
12618       case IcsPlayingWhite:
12619       case IcsPlayingBlack:
12620         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12621             SendToICS(ics_prefix);
12622             SendToICS("takeback 2\n");
12623         } else {
12624             SendToICS(ics_prefix);
12625             SendToICS("takeback 1\n");
12626         }
12627         break;
12628     }
12629 }
12630
12631 void
12632 MoveNowEvent()
12633 {
12634     ChessProgramState *cps;
12635
12636     switch (gameMode) {
12637       case MachinePlaysWhite:
12638         if (!WhiteOnMove(forwardMostMove)) {
12639             DisplayError(_("It is your turn"), 0);
12640             return;
12641         }
12642         cps = &first;
12643         break;
12644       case MachinePlaysBlack:
12645         if (WhiteOnMove(forwardMostMove)) {
12646             DisplayError(_("It is your turn"), 0);
12647             return;
12648         }
12649         cps = &first;
12650         break;
12651       case TwoMachinesPlay:
12652         if (WhiteOnMove(forwardMostMove) ==
12653             (first.twoMachinesColor[0] == 'w')) {
12654             cps = &first;
12655         } else {
12656             cps = &second;
12657         }
12658         break;
12659       case BeginningOfGame:
12660       default:
12661         return;
12662     }
12663     SendToProgram("?\n", cps);
12664 }
12665
12666 void
12667 TruncateGameEvent()
12668 {
12669     EditGameEvent();
12670     if (gameMode != EditGame) return;
12671     TruncateGame();
12672 }
12673
12674 void
12675 TruncateGame()
12676 {
12677     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12678     if (forwardMostMove > currentMove) {
12679         if (gameInfo.resultDetails != NULL) {
12680             free(gameInfo.resultDetails);
12681             gameInfo.resultDetails = NULL;
12682             gameInfo.result = GameUnfinished;
12683         }
12684         forwardMostMove = currentMove;
12685         HistorySet(parseList, backwardMostMove, forwardMostMove,
12686                    currentMove-1);
12687     }
12688 }
12689
12690 void
12691 HintEvent()
12692 {
12693     if (appData.noChessProgram) return;
12694     switch (gameMode) {
12695       case MachinePlaysWhite:
12696         if (WhiteOnMove(forwardMostMove)) {
12697             DisplayError(_("Wait until your turn"), 0);
12698             return;
12699         }
12700         break;
12701       case BeginningOfGame:
12702       case MachinePlaysBlack:
12703         if (!WhiteOnMove(forwardMostMove)) {
12704             DisplayError(_("Wait until your turn"), 0);
12705             return;
12706         }
12707         break;
12708       default:
12709         DisplayError(_("No hint available"), 0);
12710         return;
12711     }
12712     SendToProgram("hint\n", &first);
12713     hintRequested = TRUE;
12714 }
12715
12716 void
12717 BookEvent()
12718 {
12719     if (appData.noChessProgram) return;
12720     switch (gameMode) {
12721       case MachinePlaysWhite:
12722         if (WhiteOnMove(forwardMostMove)) {
12723             DisplayError(_("Wait until your turn"), 0);
12724             return;
12725         }
12726         break;
12727       case BeginningOfGame:
12728       case MachinePlaysBlack:
12729         if (!WhiteOnMove(forwardMostMove)) {
12730             DisplayError(_("Wait until your turn"), 0);
12731             return;
12732         }
12733         break;
12734       case EditPosition:
12735         EditPositionDone(TRUE);
12736         break;
12737       case TwoMachinesPlay:
12738         return;
12739       default:
12740         break;
12741     }
12742     SendToProgram("bk\n", &first);
12743     bookOutput[0] = NULLCHAR;
12744     bookRequested = TRUE;
12745 }
12746
12747 void
12748 AboutGameEvent()
12749 {
12750     char *tags = PGNTags(&gameInfo);
12751     TagsPopUp(tags, CmailMsg());
12752     free(tags);
12753 }
12754
12755 /* end button procedures */
12756
12757 void
12758 PrintPosition(fp, move)
12759      FILE *fp;
12760      int move;
12761 {
12762     int i, j;
12763     
12764     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12765         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12766             char c = PieceToChar(boards[move][i][j]);
12767             fputc(c == 'x' ? '.' : c, fp);
12768             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12769         }
12770     }
12771     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12772       fprintf(fp, "white to play\n");
12773     else
12774       fprintf(fp, "black to play\n");
12775 }
12776
12777 void
12778 PrintOpponents(fp)
12779      FILE *fp;
12780 {
12781     if (gameInfo.white != NULL) {
12782         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12783     } else {
12784         fprintf(fp, "\n");
12785     }
12786 }
12787
12788 /* Find last component of program's own name, using some heuristics */
12789 void
12790 TidyProgramName(prog, host, buf)
12791      char *prog, *host, buf[MSG_SIZ];
12792 {
12793     char *p, *q;
12794     int local = (strcmp(host, "localhost") == 0);
12795     while (!local && (p = strchr(prog, ';')) != NULL) {
12796         p++;
12797         while (*p == ' ') p++;
12798         prog = p;
12799     }
12800     if (*prog == '"' || *prog == '\'') {
12801         q = strchr(prog + 1, *prog);
12802     } else {
12803         q = strchr(prog, ' ');
12804     }
12805     if (q == NULL) q = prog + strlen(prog);
12806     p = q;
12807     while (p >= prog && *p != '/' && *p != '\\') p--;
12808     p++;
12809     if(p == prog && *p == '"') p++;
12810     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12811     memcpy(buf, p, q - p);
12812     buf[q - p] = NULLCHAR;
12813     if (!local) {
12814         strcat(buf, "@");
12815         strcat(buf, host);
12816     }
12817 }
12818
12819 char *
12820 TimeControlTagValue()
12821 {
12822     char buf[MSG_SIZ];
12823     if (!appData.clockMode) {
12824         strcpy(buf, "-");
12825     } else if (movesPerSession > 0) {
12826         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12827     } else if (timeIncrement == 0) {
12828         sprintf(buf, "%ld", timeControl/1000);
12829     } else {
12830         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12831     }
12832     return StrSave(buf);
12833 }
12834
12835 void
12836 SetGameInfo()
12837 {
12838     /* This routine is used only for certain modes */
12839     VariantClass v = gameInfo.variant;
12840     ChessMove r = GameUnfinished;
12841     char *p = NULL;
12842
12843     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12844         r = gameInfo.result; 
12845         p = gameInfo.resultDetails; 
12846         gameInfo.resultDetails = NULL;
12847     }
12848     ClearGameInfo(&gameInfo);
12849     gameInfo.variant = v;
12850
12851     switch (gameMode) {
12852       case MachinePlaysWhite:
12853         gameInfo.event = StrSave( appData.pgnEventHeader );
12854         gameInfo.site = StrSave(HostName());
12855         gameInfo.date = PGNDate();
12856         gameInfo.round = StrSave("-");
12857         gameInfo.white = StrSave(first.tidy);
12858         gameInfo.black = StrSave(UserName());
12859         gameInfo.timeControl = TimeControlTagValue();
12860         break;
12861
12862       case MachinePlaysBlack:
12863         gameInfo.event = StrSave( appData.pgnEventHeader );
12864         gameInfo.site = StrSave(HostName());
12865         gameInfo.date = PGNDate();
12866         gameInfo.round = StrSave("-");
12867         gameInfo.white = StrSave(UserName());
12868         gameInfo.black = StrSave(first.tidy);
12869         gameInfo.timeControl = TimeControlTagValue();
12870         break;
12871
12872       case TwoMachinesPlay:
12873         gameInfo.event = StrSave( appData.pgnEventHeader );
12874         gameInfo.site = StrSave(HostName());
12875         gameInfo.date = PGNDate();
12876         if (matchGame > 0) {
12877             char buf[MSG_SIZ];
12878             sprintf(buf, "%d", matchGame);
12879             gameInfo.round = StrSave(buf);
12880         } else {
12881             gameInfo.round = StrSave("-");
12882         }
12883         if (first.twoMachinesColor[0] == 'w') {
12884             gameInfo.white = StrSave(first.tidy);
12885             gameInfo.black = StrSave(second.tidy);
12886         } else {
12887             gameInfo.white = StrSave(second.tidy);
12888             gameInfo.black = StrSave(first.tidy);
12889         }
12890         gameInfo.timeControl = TimeControlTagValue();
12891         break;
12892
12893       case EditGame:
12894         gameInfo.event = StrSave("Edited game");
12895         gameInfo.site = StrSave(HostName());
12896         gameInfo.date = PGNDate();
12897         gameInfo.round = StrSave("-");
12898         gameInfo.white = StrSave("-");
12899         gameInfo.black = StrSave("-");
12900         gameInfo.result = r;
12901         gameInfo.resultDetails = p;
12902         break;
12903
12904       case EditPosition:
12905         gameInfo.event = StrSave("Edited position");
12906         gameInfo.site = StrSave(HostName());
12907         gameInfo.date = PGNDate();
12908         gameInfo.round = StrSave("-");
12909         gameInfo.white = StrSave("-");
12910         gameInfo.black = StrSave("-");
12911         break;
12912
12913       case IcsPlayingWhite:
12914       case IcsPlayingBlack:
12915       case IcsObserving:
12916       case IcsExamining:
12917         break;
12918
12919       case PlayFromGameFile:
12920         gameInfo.event = StrSave("Game from non-PGN file");
12921         gameInfo.site = StrSave(HostName());
12922         gameInfo.date = PGNDate();
12923         gameInfo.round = StrSave("-");
12924         gameInfo.white = StrSave("?");
12925         gameInfo.black = StrSave("?");
12926         break;
12927
12928       default:
12929         break;
12930     }
12931 }
12932
12933 void
12934 ReplaceComment(index, text)
12935      int index;
12936      char *text;
12937 {
12938     int len;
12939
12940     while (*text == '\n') text++;
12941     len = strlen(text);
12942     while (len > 0 && text[len - 1] == '\n') len--;
12943
12944     if (commentList[index] != NULL)
12945       free(commentList[index]);
12946
12947     if (len == 0) {
12948         commentList[index] = NULL;
12949         return;
12950     }
12951   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12952       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12953       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12954     commentList[index] = (char *) malloc(len + 2);
12955     strncpy(commentList[index], text, len);
12956     commentList[index][len] = '\n';
12957     commentList[index][len + 1] = NULLCHAR;
12958   } else { 
12959     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12960     char *p;
12961     commentList[index] = (char *) malloc(len + 6);
12962     strcpy(commentList[index], "{\n");
12963     strncpy(commentList[index]+2, text, len);
12964     commentList[index][len+2] = NULLCHAR;
12965     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12966     strcat(commentList[index], "\n}\n");
12967   }
12968 }
12969
12970 void
12971 CrushCRs(text)
12972      char *text;
12973 {
12974   char *p = text;
12975   char *q = text;
12976   char ch;
12977
12978   do {
12979     ch = *p++;
12980     if (ch == '\r') continue;
12981     *q++ = ch;
12982   } while (ch != '\0');
12983 }
12984
12985 void
12986 AppendComment(index, text, addBraces)
12987      int index;
12988      char *text;
12989      Boolean addBraces; // [HGM] braces: tells if we should add {}
12990 {
12991     int oldlen, len;
12992     char *old;
12993
12994 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12995     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12996
12997     CrushCRs(text);
12998     while (*text == '\n') text++;
12999     len = strlen(text);
13000     while (len > 0 && text[len - 1] == '\n') len--;
13001
13002     if (len == 0) return;
13003
13004     if (commentList[index] != NULL) {
13005         old = commentList[index];
13006         oldlen = strlen(old);
13007         while(commentList[index][oldlen-1] ==  '\n')
13008           commentList[index][--oldlen] = NULLCHAR;
13009         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13010         strcpy(commentList[index], old);
13011         free(old);
13012         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13013         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13014           if(addBraces) addBraces = FALSE; else { text++; len--; }
13015           while (*text == '\n') { text++; len--; }
13016           commentList[index][--oldlen] = NULLCHAR;
13017       }
13018         if(addBraces) strcat(commentList[index], "\n{\n");
13019         else          strcat(commentList[index], "\n");
13020         strcat(commentList[index], text);
13021         if(addBraces) strcat(commentList[index], "\n}\n");
13022         else          strcat(commentList[index], "\n");
13023     } else {
13024         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13025         if(addBraces)
13026              strcpy(commentList[index], "{\n");
13027         else commentList[index][0] = NULLCHAR;
13028         strcat(commentList[index], text);
13029         strcat(commentList[index], "\n");
13030         if(addBraces) strcat(commentList[index], "}\n");
13031     }
13032 }
13033
13034 static char * FindStr( char * text, char * sub_text )
13035 {
13036     char * result = strstr( text, sub_text );
13037
13038     if( result != NULL ) {
13039         result += strlen( sub_text );
13040     }
13041
13042     return result;
13043 }
13044
13045 /* [AS] Try to extract PV info from PGN comment */
13046 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13047 char *GetInfoFromComment( int index, char * text )
13048 {
13049     char * sep = text;
13050
13051     if( text != NULL && index > 0 ) {
13052         int score = 0;
13053         int depth = 0;
13054         int time = -1, sec = 0, deci;
13055         char * s_eval = FindStr( text, "[%eval " );
13056         char * s_emt = FindStr( text, "[%emt " );
13057
13058         if( s_eval != NULL || s_emt != NULL ) {
13059             /* New style */
13060             char delim;
13061
13062             if( s_eval != NULL ) {
13063                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13064                     return text;
13065                 }
13066
13067                 if( delim != ']' ) {
13068                     return text;
13069                 }
13070             }
13071
13072             if( s_emt != NULL ) {
13073             }
13074                 return text;
13075         }
13076         else {
13077             /* We expect something like: [+|-]nnn.nn/dd */
13078             int score_lo = 0;
13079
13080             if(*text != '{') return text; // [HGM] braces: must be normal comment
13081
13082             sep = strchr( text, '/' );
13083             if( sep == NULL || sep < (text+4) ) {
13084                 return text;
13085             }
13086
13087             time = -1; sec = -1; deci = -1;
13088             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13089                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13090                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13091                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13092                 return text;
13093             }
13094
13095             if( score_lo < 0 || score_lo >= 100 ) {
13096                 return text;
13097             }
13098
13099             if(sec >= 0) time = 600*time + 10*sec; else
13100             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13101
13102             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13103
13104             /* [HGM] PV time: now locate end of PV info */
13105             while( *++sep >= '0' && *sep <= '9'); // strip depth
13106             if(time >= 0)
13107             while( *++sep >= '0' && *sep <= '9'); // strip time
13108             if(sec >= 0)
13109             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13110             if(deci >= 0)
13111             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13112             while(*sep == ' ') sep++;
13113         }
13114
13115         if( depth <= 0 ) {
13116             return text;
13117         }
13118
13119         if( time < 0 ) {
13120             time = -1;
13121         }
13122
13123         pvInfoList[index-1].depth = depth;
13124         pvInfoList[index-1].score = score;
13125         pvInfoList[index-1].time  = 10*time; // centi-sec
13126         if(*sep == '}') *sep = 0; else *--sep = '{';
13127     }
13128     return sep;
13129 }
13130
13131 void
13132 SendToProgram(message, cps)
13133      char *message;
13134      ChessProgramState *cps;
13135 {
13136     int count, outCount, error;
13137     char buf[MSG_SIZ];
13138
13139     if (cps->pr == NULL) return;
13140     Attention(cps);
13141     
13142     if (appData.debugMode) {
13143         TimeMark now;
13144         GetTimeMark(&now);
13145         fprintf(debugFP, "%ld >%-6s: %s", 
13146                 SubtractTimeMarks(&now, &programStartTime),
13147                 cps->which, message);
13148     }
13149     
13150     count = strlen(message);
13151     outCount = OutputToProcess(cps->pr, message, count, &error);
13152     if (outCount < count && !exiting 
13153                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13154         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13155         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13156             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13157                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13158                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13159             } else {
13160                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13161             }
13162             gameInfo.resultDetails = StrSave(buf);
13163         }
13164         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13165     }
13166 }
13167
13168 void
13169 ReceiveFromProgram(isr, closure, message, count, error)
13170      InputSourceRef isr;
13171      VOIDSTAR closure;
13172      char *message;
13173      int count;
13174      int error;
13175 {
13176     char *end_str;
13177     char buf[MSG_SIZ];
13178     ChessProgramState *cps = (ChessProgramState *)closure;
13179
13180     if (isr != cps->isr) return; /* Killed intentionally */
13181     if (count <= 0) {
13182         if (count == 0) {
13183             sprintf(buf,
13184                     _("Error: %s chess program (%s) exited unexpectedly"),
13185                     cps->which, cps->program);
13186         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13187                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13188                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13189                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13190                 } else {
13191                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13192                 }
13193                 gameInfo.resultDetails = StrSave(buf);
13194             }
13195             RemoveInputSource(cps->isr);
13196             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13197         } else {
13198             sprintf(buf,
13199                     _("Error reading from %s chess program (%s)"),
13200                     cps->which, cps->program);
13201             RemoveInputSource(cps->isr);
13202
13203             /* [AS] Program is misbehaving badly... kill it */
13204             if( count == -2 ) {
13205                 DestroyChildProcess( cps->pr, 9 );
13206                 cps->pr = NoProc;
13207             }
13208
13209             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13210         }
13211         return;
13212     }
13213     
13214     if ((end_str = strchr(message, '\r')) != NULL)
13215       *end_str = NULLCHAR;
13216     if ((end_str = strchr(message, '\n')) != NULL)
13217       *end_str = NULLCHAR;
13218     
13219     if (appData.debugMode) {
13220         TimeMark now; int print = 1;
13221         char *quote = ""; char c; int i;
13222
13223         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13224                 char start = message[0];
13225                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13226                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13227                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13228                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13229                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13230                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13231                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13232                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13233                         { quote = "# "; print = (appData.engineComments == 2); }
13234                 message[0] = start; // restore original message
13235         }
13236         if(print) {
13237                 GetTimeMark(&now);
13238                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13239                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13240                         quote,
13241                         message);
13242         }
13243     }
13244
13245     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13246     if (appData.icsEngineAnalyze) {
13247         if (strstr(message, "whisper") != NULL ||
13248              strstr(message, "kibitz") != NULL || 
13249             strstr(message, "tellics") != NULL) return;
13250     }
13251
13252     HandleMachineMove(message, cps);
13253 }
13254
13255
13256 void
13257 SendTimeControl(cps, mps, tc, inc, sd, st)
13258      ChessProgramState *cps;
13259      int mps, inc, sd, st;
13260      long tc;
13261 {
13262     char buf[MSG_SIZ];
13263     int seconds;
13264
13265     if( timeControl_2 > 0 ) {
13266         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13267             tc = timeControl_2;
13268         }
13269     }
13270     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13271     inc /= cps->timeOdds;
13272     st  /= cps->timeOdds;
13273
13274     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13275
13276     if (st > 0) {
13277       /* Set exact time per move, normally using st command */
13278       if (cps->stKludge) {
13279         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13280         seconds = st % 60;
13281         if (seconds == 0) {
13282           sprintf(buf, "level 1 %d\n", st/60);
13283         } else {
13284           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13285         }
13286       } else {
13287         sprintf(buf, "st %d\n", st);
13288       }
13289     } else {
13290       /* Set conventional or incremental time control, using level command */
13291       if (seconds == 0) {
13292         /* Note old gnuchess bug -- minutes:seconds used to not work.
13293            Fixed in later versions, but still avoid :seconds
13294            when seconds is 0. */
13295         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13296       } else {
13297         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13298                 seconds, inc/1000);
13299       }
13300     }
13301     SendToProgram(buf, cps);
13302
13303     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13304     /* Orthogonally, limit search to given depth */
13305     if (sd > 0) {
13306       if (cps->sdKludge) {
13307         sprintf(buf, "depth\n%d\n", sd);
13308       } else {
13309         sprintf(buf, "sd %d\n", sd);
13310       }
13311       SendToProgram(buf, cps);
13312     }
13313
13314     if(cps->nps > 0) { /* [HGM] nps */
13315         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13316         else {
13317                 sprintf(buf, "nps %d\n", cps->nps);
13318               SendToProgram(buf, cps);
13319         }
13320     }
13321 }
13322
13323 ChessProgramState *WhitePlayer()
13324 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13325 {
13326     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13327        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13328         return &second;
13329     return &first;
13330 }
13331
13332 void
13333 SendTimeRemaining(cps, machineWhite)
13334      ChessProgramState *cps;
13335      int /*boolean*/ machineWhite;
13336 {
13337     char message[MSG_SIZ];
13338     long time, otime;
13339
13340     /* Note: this routine must be called when the clocks are stopped
13341        or when they have *just* been set or switched; otherwise
13342        it will be off by the time since the current tick started.
13343     */
13344     if (machineWhite) {
13345         time = whiteTimeRemaining / 10;
13346         otime = blackTimeRemaining / 10;
13347     } else {
13348         time = blackTimeRemaining / 10;
13349         otime = whiteTimeRemaining / 10;
13350     }
13351     /* [HGM] translate opponent's time by time-odds factor */
13352     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13353     if (appData.debugMode) {
13354         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13355     }
13356
13357     if (time <= 0) time = 1;
13358     if (otime <= 0) otime = 1;
13359     
13360     sprintf(message, "time %ld\n", time);
13361     SendToProgram(message, cps);
13362
13363     sprintf(message, "otim %ld\n", otime);
13364     SendToProgram(message, cps);
13365 }
13366
13367 int
13368 BoolFeature(p, name, loc, cps)
13369      char **p;
13370      char *name;
13371      int *loc;
13372      ChessProgramState *cps;
13373 {
13374   char buf[MSG_SIZ];
13375   int len = strlen(name);
13376   int val;
13377   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13378     (*p) += len + 1;
13379     sscanf(*p, "%d", &val);
13380     *loc = (val != 0);
13381     while (**p && **p != ' ') (*p)++;
13382     sprintf(buf, "accepted %s\n", name);
13383     SendToProgram(buf, cps);
13384     return TRUE;
13385   }
13386   return FALSE;
13387 }
13388
13389 int
13390 IntFeature(p, name, loc, cps)
13391      char **p;
13392      char *name;
13393      int *loc;
13394      ChessProgramState *cps;
13395 {
13396   char buf[MSG_SIZ];
13397   int len = strlen(name);
13398   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13399     (*p) += len + 1;
13400     sscanf(*p, "%d", loc);
13401     while (**p && **p != ' ') (*p)++;
13402     sprintf(buf, "accepted %s\n", name);
13403     SendToProgram(buf, cps);
13404     return TRUE;
13405   }
13406   return FALSE;
13407 }
13408
13409 int
13410 StringFeature(p, name, loc, cps)
13411      char **p;
13412      char *name;
13413      char loc[];
13414      ChessProgramState *cps;
13415 {
13416   char buf[MSG_SIZ];
13417   int len = strlen(name);
13418   if (strncmp((*p), name, len) == 0
13419       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13420     (*p) += len + 2;
13421     sscanf(*p, "%[^\"]", loc);
13422     while (**p && **p != '\"') (*p)++;
13423     if (**p == '\"') (*p)++;
13424     sprintf(buf, "accepted %s\n", name);
13425     SendToProgram(buf, cps);
13426     return TRUE;
13427   }
13428   return FALSE;
13429 }
13430
13431 int 
13432 ParseOption(Option *opt, ChessProgramState *cps)
13433 // [HGM] options: process the string that defines an engine option, and determine
13434 // name, type, default value, and allowed value range
13435 {
13436         char *p, *q, buf[MSG_SIZ];
13437         int n, min = (-1)<<31, max = 1<<31, def;
13438
13439         if(p = strstr(opt->name, " -spin ")) {
13440             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13441             if(max < min) max = min; // enforce consistency
13442             if(def < min) def = min;
13443             if(def > max) def = max;
13444             opt->value = def;
13445             opt->min = min;
13446             opt->max = max;
13447             opt->type = Spin;
13448         } else if((p = strstr(opt->name, " -slider "))) {
13449             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13450             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13451             if(max < min) max = min; // enforce consistency
13452             if(def < min) def = min;
13453             if(def > max) def = max;
13454             opt->value = def;
13455             opt->min = min;
13456             opt->max = max;
13457             opt->type = Spin; // Slider;
13458         } else if((p = strstr(opt->name, " -string "))) {
13459             opt->textValue = p+9;
13460             opt->type = TextBox;
13461         } else if((p = strstr(opt->name, " -file "))) {
13462             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13463             opt->textValue = p+7;
13464             opt->type = TextBox; // FileName;
13465         } else if((p = strstr(opt->name, " -path "))) {
13466             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13467             opt->textValue = p+7;
13468             opt->type = TextBox; // PathName;
13469         } else if(p = strstr(opt->name, " -check ")) {
13470             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13471             opt->value = (def != 0);
13472             opt->type = CheckBox;
13473         } else if(p = strstr(opt->name, " -combo ")) {
13474             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13475             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13476             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13477             opt->value = n = 0;
13478             while(q = StrStr(q, " /// ")) {
13479                 n++; *q = 0;    // count choices, and null-terminate each of them
13480                 q += 5;
13481                 if(*q == '*') { // remember default, which is marked with * prefix
13482                     q++;
13483                     opt->value = n;
13484                 }
13485                 cps->comboList[cps->comboCnt++] = q;
13486             }
13487             cps->comboList[cps->comboCnt++] = NULL;
13488             opt->max = n + 1;
13489             opt->type = ComboBox;
13490         } else if(p = strstr(opt->name, " -button")) {
13491             opt->type = Button;
13492         } else if(p = strstr(opt->name, " -save")) {
13493             opt->type = SaveButton;
13494         } else return FALSE;
13495         *p = 0; // terminate option name
13496         // now look if the command-line options define a setting for this engine option.
13497         if(cps->optionSettings && cps->optionSettings[0])
13498             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13499         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13500                 sprintf(buf, "option %s", p);
13501                 if(p = strstr(buf, ",")) *p = 0;
13502                 strcat(buf, "\n");
13503                 SendToProgram(buf, cps);
13504         }
13505         return TRUE;
13506 }
13507
13508 void
13509 FeatureDone(cps, val)
13510      ChessProgramState* cps;
13511      int val;
13512 {
13513   DelayedEventCallback cb = GetDelayedEvent();
13514   if ((cb == InitBackEnd3 && cps == &first) ||
13515       (cb == TwoMachinesEventIfReady && cps == &second)) {
13516     CancelDelayedEvent();
13517     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13518   }
13519   cps->initDone = val;
13520 }
13521
13522 /* Parse feature command from engine */
13523 void
13524 ParseFeatures(args, cps)
13525      char* args;
13526      ChessProgramState *cps;  
13527 {
13528   char *p = args;
13529   char *q;
13530   int val;
13531   char buf[MSG_SIZ];
13532
13533   for (;;) {
13534     while (*p == ' ') p++;
13535     if (*p == NULLCHAR) return;
13536
13537     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13538     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13539     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13540     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13541     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13542     if (BoolFeature(&p, "reuse", &val, cps)) {
13543       /* Engine can disable reuse, but can't enable it if user said no */
13544       if (!val) cps->reuse = FALSE;
13545       continue;
13546     }
13547     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13548     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13549       if (gameMode == TwoMachinesPlay) {
13550         DisplayTwoMachinesTitle();
13551       } else {
13552         DisplayTitle("");
13553       }
13554       continue;
13555     }
13556     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13557     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13558     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13559     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13560     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13561     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13562     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13563     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13564     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13565     if (IntFeature(&p, "done", &val, cps)) {
13566       FeatureDone(cps, val);
13567       continue;
13568     }
13569     /* Added by Tord: */
13570     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13571     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13572     /* End of additions by Tord */
13573
13574     /* [HGM] added features: */
13575     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13576     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13577     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13578     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13579     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13580     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13581     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13582         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13583             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13584             SendToProgram(buf, cps);
13585             continue;
13586         }
13587         if(cps->nrOptions >= MAX_OPTIONS) {
13588             cps->nrOptions--;
13589             sprintf(buf, "%s engine has too many options\n", cps->which);
13590             DisplayError(buf, 0);
13591         }
13592         continue;
13593     }
13594     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13595     /* End of additions by HGM */
13596
13597     /* unknown feature: complain and skip */
13598     q = p;
13599     while (*q && *q != '=') q++;
13600     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13601     SendToProgram(buf, cps);
13602     p = q;
13603     if (*p == '=') {
13604       p++;
13605       if (*p == '\"') {
13606         p++;
13607         while (*p && *p != '\"') p++;
13608         if (*p == '\"') p++;
13609       } else {
13610         while (*p && *p != ' ') p++;
13611       }
13612     }
13613   }
13614
13615 }
13616
13617 void
13618 PeriodicUpdatesEvent(newState)
13619      int newState;
13620 {
13621     if (newState == appData.periodicUpdates)
13622       return;
13623
13624     appData.periodicUpdates=newState;
13625
13626     /* Display type changes, so update it now */
13627 //    DisplayAnalysis();
13628
13629     /* Get the ball rolling again... */
13630     if (newState) {
13631         AnalysisPeriodicEvent(1);
13632         StartAnalysisClock();
13633     }
13634 }
13635
13636 void
13637 PonderNextMoveEvent(newState)
13638      int newState;
13639 {
13640     if (newState == appData.ponderNextMove) return;
13641     if (gameMode == EditPosition) EditPositionDone(TRUE);
13642     if (newState) {
13643         SendToProgram("hard\n", &first);
13644         if (gameMode == TwoMachinesPlay) {
13645             SendToProgram("hard\n", &second);
13646         }
13647     } else {
13648         SendToProgram("easy\n", &first);
13649         thinkOutput[0] = NULLCHAR;
13650         if (gameMode == TwoMachinesPlay) {
13651             SendToProgram("easy\n", &second);
13652         }
13653     }
13654     appData.ponderNextMove = newState;
13655 }
13656
13657 void
13658 NewSettingEvent(option, command, value)
13659      char *command;
13660      int option, value;
13661 {
13662     char buf[MSG_SIZ];
13663
13664     if (gameMode == EditPosition) EditPositionDone(TRUE);
13665     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13666     SendToProgram(buf, &first);
13667     if (gameMode == TwoMachinesPlay) {
13668         SendToProgram(buf, &second);
13669     }
13670 }
13671
13672 void
13673 ShowThinkingEvent()
13674 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13675 {
13676     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13677     int newState = appData.showThinking
13678         // [HGM] thinking: other features now need thinking output as well
13679         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13680     
13681     if (oldState == newState) return;
13682     oldState = newState;
13683     if (gameMode == EditPosition) EditPositionDone(TRUE);
13684     if (oldState) {
13685         SendToProgram("post\n", &first);
13686         if (gameMode == TwoMachinesPlay) {
13687             SendToProgram("post\n", &second);
13688         }
13689     } else {
13690         SendToProgram("nopost\n", &first);
13691         thinkOutput[0] = NULLCHAR;
13692         if (gameMode == TwoMachinesPlay) {
13693             SendToProgram("nopost\n", &second);
13694         }
13695     }
13696 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13697 }
13698
13699 void
13700 AskQuestionEvent(title, question, replyPrefix, which)
13701      char *title; char *question; char *replyPrefix; char *which;
13702 {
13703   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13704   if (pr == NoProc) return;
13705   AskQuestion(title, question, replyPrefix, pr);
13706 }
13707
13708 void
13709 DisplayMove(moveNumber)
13710      int moveNumber;
13711 {
13712     char message[MSG_SIZ];
13713     char res[MSG_SIZ];
13714     char cpThinkOutput[MSG_SIZ];
13715
13716     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13717     
13718     if (moveNumber == forwardMostMove - 1 || 
13719         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13720
13721         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13722
13723         if (strchr(cpThinkOutput, '\n')) {
13724             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13725         }
13726     } else {
13727         *cpThinkOutput = NULLCHAR;
13728     }
13729
13730     /* [AS] Hide thinking from human user */
13731     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13732         *cpThinkOutput = NULLCHAR;
13733         if( thinkOutput[0] != NULLCHAR ) {
13734             int i;
13735
13736             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13737                 cpThinkOutput[i] = '.';
13738             }
13739             cpThinkOutput[i] = NULLCHAR;
13740             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13741         }
13742     }
13743
13744     if (moveNumber == forwardMostMove - 1 &&
13745         gameInfo.resultDetails != NULL) {
13746         if (gameInfo.resultDetails[0] == NULLCHAR) {
13747             sprintf(res, " %s", PGNResult(gameInfo.result));
13748         } else {
13749             sprintf(res, " {%s} %s",
13750                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13751         }
13752     } else {
13753         res[0] = NULLCHAR;
13754     }
13755
13756     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13757         DisplayMessage(res, cpThinkOutput);
13758     } else {
13759         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13760                 WhiteOnMove(moveNumber) ? " " : ".. ",
13761                 parseList[moveNumber], res);
13762         DisplayMessage(message, cpThinkOutput);
13763     }
13764 }
13765
13766 void
13767 DisplayComment(moveNumber, text)
13768      int moveNumber;
13769      char *text;
13770 {
13771     char title[MSG_SIZ];
13772     char buf[8000]; // comment can be long!
13773     int score, depth;
13774     
13775     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13776       strcpy(title, "Comment");
13777     } else {
13778       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13779               WhiteOnMove(moveNumber) ? " " : ".. ",
13780               parseList[moveNumber]);
13781     }
13782     // [HGM] PV info: display PV info together with (or as) comment
13783     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13784       if(text == NULL) text = "";                                           
13785       score = pvInfoList[moveNumber].score;
13786       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13787               depth, (pvInfoList[moveNumber].time+50)/100, text);
13788       text = buf;
13789     }
13790     if (text != NULL && (appData.autoDisplayComment || commentUp))
13791         CommentPopUp(title, text);
13792 }
13793
13794 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13795  * might be busy thinking or pondering.  It can be omitted if your
13796  * gnuchess is configured to stop thinking immediately on any user
13797  * input.  However, that gnuchess feature depends on the FIONREAD
13798  * ioctl, which does not work properly on some flavors of Unix.
13799  */
13800 void
13801 Attention(cps)
13802      ChessProgramState *cps;
13803 {
13804 #if ATTENTION
13805     if (!cps->useSigint) return;
13806     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13807     switch (gameMode) {
13808       case MachinePlaysWhite:
13809       case MachinePlaysBlack:
13810       case TwoMachinesPlay:
13811       case IcsPlayingWhite:
13812       case IcsPlayingBlack:
13813       case AnalyzeMode:
13814       case AnalyzeFile:
13815         /* Skip if we know it isn't thinking */
13816         if (!cps->maybeThinking) return;
13817         if (appData.debugMode)
13818           fprintf(debugFP, "Interrupting %s\n", cps->which);
13819         InterruptChildProcess(cps->pr);
13820         cps->maybeThinking = FALSE;
13821         break;
13822       default:
13823         break;
13824     }
13825 #endif /*ATTENTION*/
13826 }
13827
13828 int
13829 CheckFlags()
13830 {
13831     if (whiteTimeRemaining <= 0) {
13832         if (!whiteFlag) {
13833             whiteFlag = TRUE;
13834             if (appData.icsActive) {
13835                 if (appData.autoCallFlag &&
13836                     gameMode == IcsPlayingBlack && !blackFlag) {
13837                   SendToICS(ics_prefix);
13838                   SendToICS("flag\n");
13839                 }
13840             } else {
13841                 if (blackFlag) {
13842                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13843                 } else {
13844                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13845                     if (appData.autoCallFlag) {
13846                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13847                         return TRUE;
13848                     }
13849                 }
13850             }
13851         }
13852     }
13853     if (blackTimeRemaining <= 0) {
13854         if (!blackFlag) {
13855             blackFlag = TRUE;
13856             if (appData.icsActive) {
13857                 if (appData.autoCallFlag &&
13858                     gameMode == IcsPlayingWhite && !whiteFlag) {
13859                   SendToICS(ics_prefix);
13860                   SendToICS("flag\n");
13861                 }
13862             } else {
13863                 if (whiteFlag) {
13864                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13865                 } else {
13866                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13867                     if (appData.autoCallFlag) {
13868                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13869                         return TRUE;
13870                     }
13871                 }
13872             }
13873         }
13874     }
13875     return FALSE;
13876 }
13877
13878 void
13879 CheckTimeControl()
13880 {
13881     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13882         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13883
13884     /*
13885      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13886      */
13887     if ( !WhiteOnMove(forwardMostMove) )
13888         /* White made time control */
13889         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13890         /* [HGM] time odds: correct new time quota for time odds! */
13891                                             / WhitePlayer()->timeOdds;
13892       else
13893         /* Black made time control */
13894         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13895                                             / WhitePlayer()->other->timeOdds;
13896 }
13897
13898 void
13899 DisplayBothClocks()
13900 {
13901     int wom = gameMode == EditPosition ?
13902       !blackPlaysFirst : WhiteOnMove(currentMove);
13903     DisplayWhiteClock(whiteTimeRemaining, wom);
13904     DisplayBlackClock(blackTimeRemaining, !wom);
13905 }
13906
13907
13908 /* Timekeeping seems to be a portability nightmare.  I think everyone
13909    has ftime(), but I'm really not sure, so I'm including some ifdefs
13910    to use other calls if you don't.  Clocks will be less accurate if
13911    you have neither ftime nor gettimeofday.
13912 */
13913
13914 /* VS 2008 requires the #include outside of the function */
13915 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13916 #include <sys/timeb.h>
13917 #endif
13918
13919 /* Get the current time as a TimeMark */
13920 void
13921 GetTimeMark(tm)
13922      TimeMark *tm;
13923 {
13924 #if HAVE_GETTIMEOFDAY
13925
13926     struct timeval timeVal;
13927     struct timezone timeZone;
13928
13929     gettimeofday(&timeVal, &timeZone);
13930     tm->sec = (long) timeVal.tv_sec; 
13931     tm->ms = (int) (timeVal.tv_usec / 1000L);
13932
13933 #else /*!HAVE_GETTIMEOFDAY*/
13934 #if HAVE_FTIME
13935
13936 // include <sys/timeb.h> / moved to just above start of function
13937     struct timeb timeB;
13938
13939     ftime(&timeB);
13940     tm->sec = (long) timeB.time;
13941     tm->ms = (int) timeB.millitm;
13942
13943 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13944     tm->sec = (long) time(NULL);
13945     tm->ms = 0;
13946 #endif
13947 #endif
13948 }
13949
13950 /* Return the difference in milliseconds between two
13951    time marks.  We assume the difference will fit in a long!
13952 */
13953 long
13954 SubtractTimeMarks(tm2, tm1)
13955      TimeMark *tm2, *tm1;
13956 {
13957     return 1000L*(tm2->sec - tm1->sec) +
13958            (long) (tm2->ms - tm1->ms);
13959 }
13960
13961
13962 /*
13963  * Code to manage the game clocks.
13964  *
13965  * In tournament play, black starts the clock and then white makes a move.
13966  * We give the human user a slight advantage if he is playing white---the
13967  * clocks don't run until he makes his first move, so it takes zero time.
13968  * Also, we don't account for network lag, so we could get out of sync
13969  * with GNU Chess's clock -- but then, referees are always right.  
13970  */
13971
13972 static TimeMark tickStartTM;
13973 static long intendedTickLength;
13974
13975 long
13976 NextTickLength(timeRemaining)
13977      long timeRemaining;
13978 {
13979     long nominalTickLength, nextTickLength;
13980
13981     if (timeRemaining > 0L && timeRemaining <= 10000L)
13982       nominalTickLength = 100L;
13983     else
13984       nominalTickLength = 1000L;
13985     nextTickLength = timeRemaining % nominalTickLength;
13986     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13987
13988     return nextTickLength;
13989 }
13990
13991 /* Adjust clock one minute up or down */
13992 void
13993 AdjustClock(Boolean which, int dir)
13994 {
13995     if(which) blackTimeRemaining += 60000*dir;
13996     else      whiteTimeRemaining += 60000*dir;
13997     DisplayBothClocks();
13998 }
13999
14000 /* Stop clocks and reset to a fresh time control */
14001 void
14002 ResetClocks() 
14003 {
14004     (void) StopClockTimer();
14005     if (appData.icsActive) {
14006         whiteTimeRemaining = blackTimeRemaining = 0;
14007     } else if (searchTime) {
14008         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14009         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14010     } else { /* [HGM] correct new time quote for time odds */
14011         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14012         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14013     }
14014     if (whiteFlag || blackFlag) {
14015         DisplayTitle("");
14016         whiteFlag = blackFlag = FALSE;
14017     }
14018     DisplayBothClocks();
14019 }
14020
14021 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14022
14023 /* Decrement running clock by amount of time that has passed */
14024 void
14025 DecrementClocks()
14026 {
14027     long timeRemaining;
14028     long lastTickLength, fudge;
14029     TimeMark now;
14030
14031     if (!appData.clockMode) return;
14032     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14033         
14034     GetTimeMark(&now);
14035
14036     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14037
14038     /* Fudge if we woke up a little too soon */
14039     fudge = intendedTickLength - lastTickLength;
14040     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14041
14042     if (WhiteOnMove(forwardMostMove)) {
14043         if(whiteNPS >= 0) lastTickLength = 0;
14044         timeRemaining = whiteTimeRemaining -= lastTickLength;
14045         DisplayWhiteClock(whiteTimeRemaining - fudge,
14046                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14047     } else {
14048         if(blackNPS >= 0) lastTickLength = 0;
14049         timeRemaining = blackTimeRemaining -= lastTickLength;
14050         DisplayBlackClock(blackTimeRemaining - fudge,
14051                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14052     }
14053
14054     if (CheckFlags()) return;
14055         
14056     tickStartTM = now;
14057     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14058     StartClockTimer(intendedTickLength);
14059
14060     /* if the time remaining has fallen below the alarm threshold, sound the
14061      * alarm. if the alarm has sounded and (due to a takeback or time control
14062      * with increment) the time remaining has increased to a level above the
14063      * threshold, reset the alarm so it can sound again. 
14064      */
14065     
14066     if (appData.icsActive && appData.icsAlarm) {
14067
14068         /* make sure we are dealing with the user's clock */
14069         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14070                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14071            )) return;
14072
14073         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14074             alarmSounded = FALSE;
14075         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14076             PlayAlarmSound();
14077             alarmSounded = TRUE;
14078         }
14079     }
14080 }
14081
14082
14083 /* A player has just moved, so stop the previously running
14084    clock and (if in clock mode) start the other one.
14085    We redisplay both clocks in case we're in ICS mode, because
14086    ICS gives us an update to both clocks after every move.
14087    Note that this routine is called *after* forwardMostMove
14088    is updated, so the last fractional tick must be subtracted
14089    from the color that is *not* on move now.
14090 */
14091 void
14092 SwitchClocks(int newMoveNr)
14093 {
14094     long lastTickLength;
14095     TimeMark now;
14096     int flagged = FALSE;
14097
14098     GetTimeMark(&now);
14099
14100     if (StopClockTimer() && appData.clockMode) {
14101         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14102         if (!WhiteOnMove(forwardMostMove)) {
14103             if(blackNPS >= 0) lastTickLength = 0;
14104             blackTimeRemaining -= lastTickLength;
14105            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14106 //         if(pvInfoList[forwardMostMove-1].time == -1)
14107                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14108                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14109         } else {
14110            if(whiteNPS >= 0) lastTickLength = 0;
14111            whiteTimeRemaining -= lastTickLength;
14112            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14113 //         if(pvInfoList[forwardMostMove-1].time == -1)
14114                  pvInfoList[forwardMostMove-1].time = 
14115                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14116         }
14117         flagged = CheckFlags();
14118     }
14119     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14120     CheckTimeControl();
14121
14122     if (flagged || !appData.clockMode) return;
14123
14124     switch (gameMode) {
14125       case MachinePlaysBlack:
14126       case MachinePlaysWhite:
14127       case BeginningOfGame:
14128         if (pausing) return;
14129         break;
14130
14131       case EditGame:
14132       case PlayFromGameFile:
14133       case IcsExamining:
14134         return;
14135
14136       default:
14137         break;
14138     }
14139
14140     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14141         if(WhiteOnMove(forwardMostMove))
14142              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14143         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14144     }
14145
14146     tickStartTM = now;
14147     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14148       whiteTimeRemaining : blackTimeRemaining);
14149     StartClockTimer(intendedTickLength);
14150 }
14151         
14152
14153 /* Stop both clocks */
14154 void
14155 StopClocks()
14156 {       
14157     long lastTickLength;
14158     TimeMark now;
14159
14160     if (!StopClockTimer()) return;
14161     if (!appData.clockMode) return;
14162
14163     GetTimeMark(&now);
14164
14165     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14166     if (WhiteOnMove(forwardMostMove)) {
14167         if(whiteNPS >= 0) lastTickLength = 0;
14168         whiteTimeRemaining -= lastTickLength;
14169         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14170     } else {
14171         if(blackNPS >= 0) lastTickLength = 0;
14172         blackTimeRemaining -= lastTickLength;
14173         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14174     }
14175     CheckFlags();
14176 }
14177         
14178 /* Start clock of player on move.  Time may have been reset, so
14179    if clock is already running, stop and restart it. */
14180 void
14181 StartClocks()
14182 {
14183     (void) StopClockTimer(); /* in case it was running already */
14184     DisplayBothClocks();
14185     if (CheckFlags()) return;
14186
14187     if (!appData.clockMode) return;
14188     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14189
14190     GetTimeMark(&tickStartTM);
14191     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14192       whiteTimeRemaining : blackTimeRemaining);
14193
14194    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14195     whiteNPS = blackNPS = -1; 
14196     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14197        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14198         whiteNPS = first.nps;
14199     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14200        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14201         blackNPS = first.nps;
14202     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14203         whiteNPS = second.nps;
14204     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14205         blackNPS = second.nps;
14206     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14207
14208     StartClockTimer(intendedTickLength);
14209 }
14210
14211 char *
14212 TimeString(ms)
14213      long ms;
14214 {
14215     long second, minute, hour, day;
14216     char *sign = "";
14217     static char buf[32];
14218     
14219     if (ms > 0 && ms <= 9900) {
14220       /* convert milliseconds to tenths, rounding up */
14221       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14222
14223       sprintf(buf, " %03.1f ", tenths/10.0);
14224       return buf;
14225     }
14226
14227     /* convert milliseconds to seconds, rounding up */
14228     /* use floating point to avoid strangeness of integer division
14229        with negative dividends on many machines */
14230     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14231
14232     if (second < 0) {
14233         sign = "-";
14234         second = -second;
14235     }
14236     
14237     day = second / (60 * 60 * 24);
14238     second = second % (60 * 60 * 24);
14239     hour = second / (60 * 60);
14240     second = second % (60 * 60);
14241     minute = second / 60;
14242     second = second % 60;
14243     
14244     if (day > 0)
14245       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14246               sign, day, hour, minute, second);
14247     else if (hour > 0)
14248       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14249     else
14250       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14251     
14252     return buf;
14253 }
14254
14255
14256 /*
14257  * This is necessary because some C libraries aren't ANSI C compliant yet.
14258  */
14259 char *
14260 StrStr(string, match)
14261      char *string, *match;
14262 {
14263     int i, length;
14264     
14265     length = strlen(match);
14266     
14267     for (i = strlen(string) - length; i >= 0; i--, string++)
14268       if (!strncmp(match, string, length))
14269         return string;
14270     
14271     return NULL;
14272 }
14273
14274 char *
14275 StrCaseStr(string, match)
14276      char *string, *match;
14277 {
14278     int i, j, length;
14279     
14280     length = strlen(match);
14281     
14282     for (i = strlen(string) - length; i >= 0; i--, string++) {
14283         for (j = 0; j < length; j++) {
14284             if (ToLower(match[j]) != ToLower(string[j]))
14285               break;
14286         }
14287         if (j == length) return string;
14288     }
14289
14290     return NULL;
14291 }
14292
14293 #ifndef _amigados
14294 int
14295 StrCaseCmp(s1, s2)
14296      char *s1, *s2;
14297 {
14298     char c1, c2;
14299     
14300     for (;;) {
14301         c1 = ToLower(*s1++);
14302         c2 = ToLower(*s2++);
14303         if (c1 > c2) return 1;
14304         if (c1 < c2) return -1;
14305         if (c1 == NULLCHAR) return 0;
14306     }
14307 }
14308
14309
14310 int
14311 ToLower(c)
14312      int c;
14313 {
14314     return isupper(c) ? tolower(c) : c;
14315 }
14316
14317
14318 int
14319 ToUpper(c)
14320      int c;
14321 {
14322     return islower(c) ? toupper(c) : c;
14323 }
14324 #endif /* !_amigados    */
14325
14326 char *
14327 StrSave(s)
14328      char *s;
14329 {
14330     char *ret;
14331
14332     if ((ret = (char *) malloc(strlen(s) + 1))) {
14333         strcpy(ret, s);
14334     }
14335     return ret;
14336 }
14337
14338 char *
14339 StrSavePtr(s, savePtr)
14340      char *s, **savePtr;
14341 {
14342     if (*savePtr) {
14343         free(*savePtr);
14344     }
14345     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14346         strcpy(*savePtr, s);
14347     }
14348     return(*savePtr);
14349 }
14350
14351 char *
14352 PGNDate()
14353 {
14354     time_t clock;
14355     struct tm *tm;
14356     char buf[MSG_SIZ];
14357
14358     clock = time((time_t *)NULL);
14359     tm = localtime(&clock);
14360     sprintf(buf, "%04d.%02d.%02d",
14361             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14362     return StrSave(buf);
14363 }
14364
14365
14366 char *
14367 PositionToFEN(move, overrideCastling)
14368      int move;
14369      char *overrideCastling;
14370 {
14371     int i, j, fromX, fromY, toX, toY;
14372     int whiteToPlay;
14373     char buf[128];
14374     char *p, *q;
14375     int emptycount;
14376     ChessSquare piece;
14377
14378     whiteToPlay = (gameMode == EditPosition) ?
14379       !blackPlaysFirst : (move % 2 == 0);
14380     p = buf;
14381
14382     /* Piece placement data */
14383     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14384         emptycount = 0;
14385         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14386             if (boards[move][i][j] == EmptySquare) {
14387                 emptycount++;
14388             } else { ChessSquare piece = boards[move][i][j];
14389                 if (emptycount > 0) {
14390                     if(emptycount<10) /* [HGM] can be >= 10 */
14391                         *p++ = '0' + emptycount;
14392                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14393                     emptycount = 0;
14394                 }
14395                 if(PieceToChar(piece) == '+') {
14396                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14397                     *p++ = '+';
14398                     piece = (ChessSquare)(DEMOTED piece);
14399                 } 
14400                 *p++ = PieceToChar(piece);
14401                 if(p[-1] == '~') {
14402                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14403                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14404                     *p++ = '~';
14405                 }
14406             }
14407         }
14408         if (emptycount > 0) {
14409             if(emptycount<10) /* [HGM] can be >= 10 */
14410                 *p++ = '0' + emptycount;
14411             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14412             emptycount = 0;
14413         }
14414         *p++ = '/';
14415     }
14416     *(p - 1) = ' ';
14417
14418     /* [HGM] print Crazyhouse or Shogi holdings */
14419     if( gameInfo.holdingsWidth ) {
14420         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14421         q = p;
14422         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14423             piece = boards[move][i][BOARD_WIDTH-1];
14424             if( piece != EmptySquare )
14425               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14426                   *p++ = PieceToChar(piece);
14427         }
14428         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14429             piece = boards[move][BOARD_HEIGHT-i-1][0];
14430             if( piece != EmptySquare )
14431               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14432                   *p++ = PieceToChar(piece);
14433         }
14434
14435         if( q == p ) *p++ = '-';
14436         *p++ = ']';
14437         *p++ = ' ';
14438     }
14439
14440     /* Active color */
14441     *p++ = whiteToPlay ? 'w' : 'b';
14442     *p++ = ' ';
14443
14444   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14445     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14446   } else {
14447   if(nrCastlingRights) {
14448      q = p;
14449      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14450        /* [HGM] write directly from rights */
14451            if(boards[move][CASTLING][2] != NoRights &&
14452               boards[move][CASTLING][0] != NoRights   )
14453                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14454            if(boards[move][CASTLING][2] != NoRights &&
14455               boards[move][CASTLING][1] != NoRights   )
14456                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14457            if(boards[move][CASTLING][5] != NoRights &&
14458               boards[move][CASTLING][3] != NoRights   )
14459                 *p++ = boards[move][CASTLING][3] + AAA;
14460            if(boards[move][CASTLING][5] != NoRights &&
14461               boards[move][CASTLING][4] != NoRights   )
14462                 *p++ = boards[move][CASTLING][4] + AAA;
14463      } else {
14464
14465         /* [HGM] write true castling rights */
14466         if( nrCastlingRights == 6 ) {
14467             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14468                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14469             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14470                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14471             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14472                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14473             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14474                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14475         }
14476      }
14477      if (q == p) *p++ = '-'; /* No castling rights */
14478      *p++ = ' ';
14479   }
14480
14481   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14482      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14483     /* En passant target square */
14484     if (move > backwardMostMove) {
14485         fromX = moveList[move - 1][0] - AAA;
14486         fromY = moveList[move - 1][1] - ONE;
14487         toX = moveList[move - 1][2] - AAA;
14488         toY = moveList[move - 1][3] - ONE;
14489         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14490             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14491             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14492             fromX == toX) {
14493             /* 2-square pawn move just happened */
14494             *p++ = toX + AAA;
14495             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14496         } else {
14497             *p++ = '-';
14498         }
14499     } else if(move == backwardMostMove) {
14500         // [HGM] perhaps we should always do it like this, and forget the above?
14501         if((signed char)boards[move][EP_STATUS] >= 0) {
14502             *p++ = boards[move][EP_STATUS] + AAA;
14503             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14504         } else {
14505             *p++ = '-';
14506         }
14507     } else {
14508         *p++ = '-';
14509     }
14510     *p++ = ' ';
14511   }
14512   }
14513
14514     /* [HGM] find reversible plies */
14515     {   int i = 0, j=move;
14516
14517         if (appData.debugMode) { int k;
14518             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14519             for(k=backwardMostMove; k<=forwardMostMove; k++)
14520                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14521
14522         }
14523
14524         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14525         if( j == backwardMostMove ) i += initialRulePlies;
14526         sprintf(p, "%d ", i);
14527         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14528     }
14529     /* Fullmove number */
14530     sprintf(p, "%d", (move / 2) + 1);
14531     
14532     return StrSave(buf);
14533 }
14534
14535 Boolean
14536 ParseFEN(board, blackPlaysFirst, fen)
14537     Board board;
14538      int *blackPlaysFirst;
14539      char *fen;
14540 {
14541     int i, j;
14542     char *p;
14543     int emptycount;
14544     ChessSquare piece;
14545
14546     p = fen;
14547
14548     /* [HGM] by default clear Crazyhouse holdings, if present */
14549     if(gameInfo.holdingsWidth) {
14550        for(i=0; i<BOARD_HEIGHT; i++) {
14551            board[i][0]             = EmptySquare; /* black holdings */
14552            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14553            board[i][1]             = (ChessSquare) 0; /* black counts */
14554            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14555        }
14556     }
14557
14558     /* Piece placement data */
14559     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14560         j = 0;
14561         for (;;) {
14562             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14563                 if (*p == '/') p++;
14564                 emptycount = gameInfo.boardWidth - j;
14565                 while (emptycount--)
14566                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14567                 break;
14568 #if(BOARD_FILES >= 10)
14569             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14570                 p++; emptycount=10;
14571                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14572                 while (emptycount--)
14573                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14574 #endif
14575             } else if (isdigit(*p)) {
14576                 emptycount = *p++ - '0';
14577                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14578                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14579                 while (emptycount--)
14580                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14581             } else if (*p == '+' || isalpha(*p)) {
14582                 if (j >= gameInfo.boardWidth) return FALSE;
14583                 if(*p=='+') {
14584                     piece = CharToPiece(*++p);
14585                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14586                     piece = (ChessSquare) (PROMOTED piece ); p++;
14587                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14588                 } else piece = CharToPiece(*p++);
14589
14590                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14591                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14592                     piece = (ChessSquare) (PROMOTED piece);
14593                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14594                     p++;
14595                 }
14596                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14597             } else {
14598                 return FALSE;
14599             }
14600         }
14601     }
14602     while (*p == '/' || *p == ' ') p++;
14603
14604     /* [HGM] look for Crazyhouse holdings here */
14605     while(*p==' ') p++;
14606     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14607         if(*p == '[') p++;
14608         if(*p == '-' ) *p++; /* empty holdings */ else {
14609             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14610             /* if we would allow FEN reading to set board size, we would   */
14611             /* have to add holdings and shift the board read so far here   */
14612             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14613                 *p++;
14614                 if((int) piece >= (int) BlackPawn ) {
14615                     i = (int)piece - (int)BlackPawn;
14616                     i = PieceToNumber((ChessSquare)i);
14617                     if( i >= gameInfo.holdingsSize ) return FALSE;
14618                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14619                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14620                 } else {
14621                     i = (int)piece - (int)WhitePawn;
14622                     i = PieceToNumber((ChessSquare)i);
14623                     if( i >= gameInfo.holdingsSize ) return FALSE;
14624                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14625                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14626                 }
14627             }
14628         }
14629         if(*p == ']') *p++;
14630     }
14631
14632     while(*p == ' ') p++;
14633
14634     /* Active color */
14635     switch (*p++) {
14636       case 'w':
14637         *blackPlaysFirst = FALSE;
14638         break;
14639       case 'b': 
14640         *blackPlaysFirst = TRUE;
14641         break;
14642       default:
14643         return FALSE;
14644     }
14645
14646     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14647     /* return the extra info in global variiables             */
14648
14649     /* set defaults in case FEN is incomplete */
14650     board[EP_STATUS] = EP_UNKNOWN;
14651     for(i=0; i<nrCastlingRights; i++ ) {
14652         board[CASTLING][i] =
14653             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14654     }   /* assume possible unless obviously impossible */
14655     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14656     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14657     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14658                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14659     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14660     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14661     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14662                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14663     FENrulePlies = 0;
14664
14665     while(*p==' ') p++;
14666     if(nrCastlingRights) {
14667       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14668           /* castling indicator present, so default becomes no castlings */
14669           for(i=0; i<nrCastlingRights; i++ ) {
14670                  board[CASTLING][i] = NoRights;
14671           }
14672       }
14673       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14674              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14675              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14676              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14677         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14678
14679         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14680             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14681             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14682         }
14683         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14684             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14685         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14686                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14687         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14688                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14689         switch(c) {
14690           case'K':
14691               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14692               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14693               board[CASTLING][2] = whiteKingFile;
14694               break;
14695           case'Q':
14696               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14697               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14698               board[CASTLING][2] = whiteKingFile;
14699               break;
14700           case'k':
14701               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14702               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14703               board[CASTLING][5] = blackKingFile;
14704               break;
14705           case'q':
14706               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14707               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14708               board[CASTLING][5] = blackKingFile;
14709           case '-':
14710               break;
14711           default: /* FRC castlings */
14712               if(c >= 'a') { /* black rights */
14713                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14714                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14715                   if(i == BOARD_RGHT) break;
14716                   board[CASTLING][5] = i;
14717                   c -= AAA;
14718                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14719                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14720                   if(c > i)
14721                       board[CASTLING][3] = c;
14722                   else
14723                       board[CASTLING][4] = c;
14724               } else { /* white rights */
14725                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14726                     if(board[0][i] == WhiteKing) break;
14727                   if(i == BOARD_RGHT) break;
14728                   board[CASTLING][2] = i;
14729                   c -= AAA - 'a' + 'A';
14730                   if(board[0][c] >= WhiteKing) break;
14731                   if(c > i)
14732                       board[CASTLING][0] = c;
14733                   else
14734                       board[CASTLING][1] = c;
14735               }
14736         }
14737       }
14738       for(i=0; i<nrCastlingRights; i++)
14739         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14740     if (appData.debugMode) {
14741         fprintf(debugFP, "FEN castling rights:");
14742         for(i=0; i<nrCastlingRights; i++)
14743         fprintf(debugFP, " %d", board[CASTLING][i]);
14744         fprintf(debugFP, "\n");
14745     }
14746
14747       while(*p==' ') p++;
14748     }
14749
14750     /* read e.p. field in games that know e.p. capture */
14751     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14752        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14753       if(*p=='-') {
14754         p++; board[EP_STATUS] = EP_NONE;
14755       } else {
14756          char c = *p++ - AAA;
14757
14758          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14759          if(*p >= '0' && *p <='9') *p++;
14760          board[EP_STATUS] = c;
14761       }
14762     }
14763
14764
14765     if(sscanf(p, "%d", &i) == 1) {
14766         FENrulePlies = i; /* 50-move ply counter */
14767         /* (The move number is still ignored)    */
14768     }
14769
14770     return TRUE;
14771 }
14772       
14773 void
14774 EditPositionPasteFEN(char *fen)
14775 {
14776   if (fen != NULL) {
14777     Board initial_position;
14778
14779     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14780       DisplayError(_("Bad FEN position in clipboard"), 0);
14781       return ;
14782     } else {
14783       int savedBlackPlaysFirst = blackPlaysFirst;
14784       EditPositionEvent();
14785       blackPlaysFirst = savedBlackPlaysFirst;
14786       CopyBoard(boards[0], initial_position);
14787       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14788       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14789       DisplayBothClocks();
14790       DrawPosition(FALSE, boards[currentMove]);
14791     }
14792   }
14793 }
14794
14795 static char cseq[12] = "\\   ";
14796
14797 Boolean set_cont_sequence(char *new_seq)
14798 {
14799     int len;
14800     Boolean ret;
14801
14802     // handle bad attempts to set the sequence
14803         if (!new_seq)
14804                 return 0; // acceptable error - no debug
14805
14806     len = strlen(new_seq);
14807     ret = (len > 0) && (len < sizeof(cseq));
14808     if (ret)
14809         strcpy(cseq, new_seq);
14810     else if (appData.debugMode)
14811         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14812     return ret;
14813 }
14814
14815 /*
14816     reformat a source message so words don't cross the width boundary.  internal
14817     newlines are not removed.  returns the wrapped size (no null character unless
14818     included in source message).  If dest is NULL, only calculate the size required
14819     for the dest buffer.  lp argument indicats line position upon entry, and it's
14820     passed back upon exit.
14821 */
14822 int wrap(char *dest, char *src, int count, int width, int *lp)
14823 {
14824     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14825
14826     cseq_len = strlen(cseq);
14827     old_line = line = *lp;
14828     ansi = len = clen = 0;
14829
14830     for (i=0; i < count; i++)
14831     {
14832         if (src[i] == '\033')
14833             ansi = 1;
14834
14835         // if we hit the width, back up
14836         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14837         {
14838             // store i & len in case the word is too long
14839             old_i = i, old_len = len;
14840
14841             // find the end of the last word
14842             while (i && src[i] != ' ' && src[i] != '\n')
14843             {
14844                 i--;
14845                 len--;
14846             }
14847
14848             // word too long?  restore i & len before splitting it
14849             if ((old_i-i+clen) >= width)
14850             {
14851                 i = old_i;
14852                 len = old_len;
14853             }
14854
14855             // extra space?
14856             if (i && src[i-1] == ' ')
14857                 len--;
14858
14859             if (src[i] != ' ' && src[i] != '\n')
14860             {
14861                 i--;
14862                 if (len)
14863                     len--;
14864             }
14865
14866             // now append the newline and continuation sequence
14867             if (dest)
14868                 dest[len] = '\n';
14869             len++;
14870             if (dest)
14871                 strncpy(dest+len, cseq, cseq_len);
14872             len += cseq_len;
14873             line = cseq_len;
14874             clen = cseq_len;
14875             continue;
14876         }
14877
14878         if (dest)
14879             dest[len] = src[i];
14880         len++;
14881         if (!ansi)
14882             line++;
14883         if (src[i] == '\n')
14884             line = 0;
14885         if (src[i] == 'm')
14886             ansi = 0;
14887     }
14888     if (dest && appData.debugMode)
14889     {
14890         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14891             count, width, line, len, *lp);
14892         show_bytes(debugFP, src, count);
14893         fprintf(debugFP, "\ndest: ");
14894         show_bytes(debugFP, dest, len);
14895         fprintf(debugFP, "\n");
14896     }
14897     *lp = dest ? line : old_line;
14898
14899     return len;
14900 }
14901
14902 // [HGM] vari: routines for shelving variations
14903
14904 void 
14905 PushTail(int firstMove, int lastMove)
14906 {
14907         int i, j, nrMoves = lastMove - firstMove;
14908
14909         if(appData.icsActive) { // only in local mode
14910                 forwardMostMove = currentMove; // mimic old ICS behavior
14911                 return;
14912         }
14913         if(storedGames >= MAX_VARIATIONS-1) return;
14914
14915         // push current tail of game on stack
14916         savedResult[storedGames] = gameInfo.result;
14917         savedDetails[storedGames] = gameInfo.resultDetails;
14918         gameInfo.resultDetails = NULL;
14919         savedFirst[storedGames] = firstMove;
14920         savedLast [storedGames] = lastMove;
14921         savedFramePtr[storedGames] = framePtr;
14922         framePtr -= nrMoves; // reserve space for the boards
14923         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14924             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14925             for(j=0; j<MOVE_LEN; j++)
14926                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14927             for(j=0; j<2*MOVE_LEN; j++)
14928                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14929             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14930             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14931             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14932             pvInfoList[firstMove+i-1].depth = 0;
14933             commentList[framePtr+i] = commentList[firstMove+i];
14934             commentList[firstMove+i] = NULL;
14935         }
14936
14937         storedGames++;
14938         forwardMostMove = currentMove; // truncte game so we can start variation
14939         if(storedGames == 1) GreyRevert(FALSE);
14940 }
14941
14942 Boolean
14943 PopTail(Boolean annotate)
14944 {
14945         int i, j, nrMoves;
14946         char buf[8000], moveBuf[20];
14947
14948         if(appData.icsActive) return FALSE; // only in local mode
14949         if(!storedGames) return FALSE; // sanity
14950
14951         storedGames--;
14952         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14953         nrMoves = savedLast[storedGames] - currentMove;
14954         if(annotate) {
14955                 int cnt = 10;
14956                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14957                 else strcpy(buf, "(");
14958                 for(i=currentMove; i<forwardMostMove; i++) {
14959                         if(WhiteOnMove(i))
14960                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14961                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14962                         strcat(buf, moveBuf);
14963                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14964                 }
14965                 strcat(buf, ")");
14966         }
14967         for(i=1; i<nrMoves; i++) { // copy last variation back
14968             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14969             for(j=0; j<MOVE_LEN; j++)
14970                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14971             for(j=0; j<2*MOVE_LEN; j++)
14972                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14973             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14974             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14975             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14976             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14977             commentList[currentMove+i] = commentList[framePtr+i];
14978             commentList[framePtr+i] = NULL;
14979         }
14980         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14981         framePtr = savedFramePtr[storedGames];
14982         gameInfo.result = savedResult[storedGames];
14983         if(gameInfo.resultDetails != NULL) {
14984             free(gameInfo.resultDetails);
14985       }
14986         gameInfo.resultDetails = savedDetails[storedGames];
14987         forwardMostMove = currentMove + nrMoves;
14988         if(storedGames == 0) GreyRevert(TRUE);
14989         return TRUE;
14990 }
14991
14992 void 
14993 CleanupTail()
14994 {       // remove all shelved variations
14995         int i;
14996         for(i=0; i<storedGames; i++) {
14997             if(savedDetails[i])
14998                 free(savedDetails[i]);
14999             savedDetails[i] = NULL;
15000         }
15001         for(i=framePtr; i<MAX_MOVES; i++) {
15002                 if(commentList[i]) free(commentList[i]);
15003                 commentList[i] = NULL;
15004         }
15005         framePtr = MAX_MOVES-1;
15006         storedGames = 0;
15007 }