fe325dbb9d3dca87b97f3125995d84bd8c0cb2d9
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 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((void));
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 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
254 int chattingPartner;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
256
257 /* States for ics_getting_history */
258 #define H_FALSE 0
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
264
265 /* whosays values for GameEnds */
266 #define GE_ICS 0
267 #define GE_ENGINE 1
268 #define GE_PLAYER 2
269 #define GE_FILE 3
270 #define GE_XBOARD 4
271 #define GE_ENGINE1 5
272 #define GE_ENGINE2 6
273
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
276
277 /* Different types of move when calling RegisterMove */
278 #define CMAIL_MOVE   0
279 #define CMAIL_RESIGN 1
280 #define CMAIL_DRAW   2
281 #define CMAIL_ACCEPT 3
282
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
287
288 /* Telnet protocol constants */
289 #define TN_WILL 0373
290 #define TN_WONT 0374
291 #define TN_DO   0375
292 #define TN_DONT 0376
293 #define TN_IAC  0377
294 #define TN_ECHO 0001
295 #define TN_SGA  0003
296 #define TN_PORT 23
297
298 /* [AS] */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
300 {
301     assert( dst != NULL );
302     assert( src != NULL );
303     assert( count > 0 );
304
305     strncpy( dst, src, count );
306     dst[ count-1 ] = '\0';
307     return dst;
308 }
309
310 /* Some compiler can't cast u64 to double
311  * This function do the job for us:
312
313  * We use the highest bit for cast, this only
314  * works if the highest bit is not
315  * in use (This should not happen)
316  *
317  * We used this for all compiler
318  */
319 double
320 u64ToDouble(u64 value)
321 {
322   double r;
323   u64 tmp = value & u64Const(0x7fffffffffffffff);
324   r = (double)(s64)tmp;
325   if (value & u64Const(0x8000000000000000))
326        r +=  9.2233720368547758080e18; /* 2^63 */
327  return r;
328 }
329
330 /* Fake up flags for now, as we aren't keeping track of castling
331    availability yet. [HGM] Change of logic: the flag now only
332    indicates the type of castlings allowed by the rule of the game.
333    The actual rights themselves are maintained in the array
334    castlingRights, as part of the game history, and are not probed
335    by this function.
336  */
337 int
338 PosFlags(index)
339 {
340   int flags = F_ALL_CASTLE_OK;
341   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342   switch (gameInfo.variant) {
343   case VariantSuicide:
344     flags &= ~F_ALL_CASTLE_OK;
345   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346     flags |= F_IGNORE_CHECK;
347   case VariantLosers:
348     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
349     break;
350   case VariantAtomic:
351     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
352     break;
353   case VariantKriegspiel:
354     flags |= F_KRIEGSPIEL_CAPTURE;
355     break;
356   case VariantCapaRandom: 
357   case VariantFischeRandom:
358     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359   case VariantNoCastle:
360   case VariantShatranj:
361   case VariantCourier:
362   case VariantMakruk:
363     flags &= ~F_ALL_CASTLE_OK;
364     break;
365   default:
366     break;
367   }
368   return flags;
369 }
370
371 FILE *gameFileFP, *debugFP;
372
373 /* 
374     [AS] Note: sometimes, the sscanf() function is used to parse the input
375     into a fixed-size buffer. Because of this, we must be prepared to
376     receive strings as long as the size of the input buffer, which is currently
377     set to 4K for Windows and 8K for the rest.
378     So, we must either allocate sufficiently large buffers here, or
379     reduce the size of the input buffer in the input reading part.
380 */
381
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
385
386 ChessProgramState first, second;
387
388 /* premove variables */
389 int premoveToX = 0;
390 int premoveToY = 0;
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
394 int gotPremove = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
397
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
400
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
428 int movesPerSession;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
433 int matchGame = 0;
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
437
438 /* animateTraining preserves the state of appData.animate
439  * when Training mode is activated. This allows the
440  * response to be animated when appData.animate == TRUE and
441  * appData.animateDragging == TRUE.
442  */
443 Boolean animateTraining;
444
445 GameInfo gameInfo;
446
447 AppData appData;
448
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char  initialRights[BOARD_FILES];
453 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int   initialRulePlies, FENrulePlies;
455 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
456 int loadFlag = 0; 
457 int shuffleOpenings;
458 int mute; // mute all sounds
459
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
463 int storedGames = 0;
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
469
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
473
474 ChessSquare  FIDEArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackKing, BlackKnight, BlackRook }
486 };
487
488 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
489     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491     { BlackRook, BlackMan, BlackBishop, BlackQueen,
492         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
493 };
494
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
500 };
501
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackMan, BlackFerz,
513         BlackKing, BlackMan, BlackKnight, BlackRook }
514 };
515
516
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
523 };
524
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
530 };
531
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
534         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
536         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
537 };
538
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
541         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
543         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
544 };
545
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
548         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
550         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
551 };
552
553 #ifdef GOTHIC
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
556         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
558         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !GOTHIC
561 #define GothicArray CapablancaArray
562 #endif // !GOTHIC
563
564 #ifdef FALCON
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
567         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
569         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
570 };
571 #else // !FALCON
572 #define FalconArray CapablancaArray
573 #endif // !FALCON
574
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
581
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
588 };
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
592
593
594 Board initialPosition;
595
596
597 /* Convert str to a rating. Checks for special cases of "----",
598
599    "++++", etc. Also strips ()'s */
600 int
601 string_to_rating(str)
602   char *str;
603 {
604   while(*str && !isdigit(*str)) ++str;
605   if (!*str)
606     return 0;   /* One of the special "no rating" cases */
607   else
608     return atoi(str);
609 }
610
611 void
612 ClearProgramStats()
613 {
614     /* Init programStats */
615     programStats.movelist[0] = 0;
616     programStats.depth = 0;
617     programStats.nr_moves = 0;
618     programStats.moves_left = 0;
619     programStats.nodes = 0;
620     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
621     programStats.score = 0;
622     programStats.got_only_move = 0;
623     programStats.got_fail = 0;
624     programStats.line_is_book = 0;
625 }
626
627 void
628 InitBackEnd1()
629 {
630     int matched, min, sec;
631
632     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
633
634     GetTimeMark(&programStartTime);
635     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
636
637     ClearProgramStats();
638     programStats.ok_to_send = 1;
639     programStats.seen_stat = 0;
640
641     /*
642      * Initialize game list
643      */
644     ListNew(&gameList);
645
646
647     /*
648      * Internet chess server status
649      */
650     if (appData.icsActive) {
651         appData.matchMode = FALSE;
652         appData.matchGames = 0;
653 #if ZIPPY       
654         appData.noChessProgram = !appData.zippyPlay;
655 #else
656         appData.zippyPlay = FALSE;
657         appData.zippyTalk = FALSE;
658         appData.noChessProgram = TRUE;
659 #endif
660         if (*appData.icsHelper != NULLCHAR) {
661             appData.useTelnet = TRUE;
662             appData.telnetProgram = appData.icsHelper;
663         }
664     } else {
665         appData.zippyTalk = appData.zippyPlay = FALSE;
666     }
667
668     /* [AS] Initialize pv info list [HGM] and game state */
669     {
670         int i, j;
671
672         for( i=0; i<=framePtr; i++ ) {
673             pvInfoList[i].depth = -1;
674             boards[i][EP_STATUS] = EP_NONE;
675             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
676         }
677     }
678
679     /*
680      * Parse timeControl resource
681      */
682     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683                           appData.movesPerSession)) {
684         char buf[MSG_SIZ];
685         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686         DisplayFatalError(buf, 0, 2);
687     }
688
689     /*
690      * Parse searchTime resource
691      */
692     if (*appData.searchTime != NULLCHAR) {
693         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
694         if (matched == 1) {
695             searchTime = min * 60;
696         } else if (matched == 2) {
697             searchTime = min * 60 + sec;
698         } else {
699             char buf[MSG_SIZ];
700             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701             DisplayFatalError(buf, 0, 2);
702         }
703     }
704
705     /* [AS] Adjudication threshold */
706     adjudicateLossThreshold = appData.adjudicateLossThreshold;
707     
708     first.which = "first";
709     second.which = "second";
710     first.maybeThinking = second.maybeThinking = FALSE;
711     first.pr = second.pr = NoProc;
712     first.isr = second.isr = NULL;
713     first.sendTime = second.sendTime = 2;
714     first.sendDrawOffers = 1;
715     if (appData.firstPlaysBlack) {
716         first.twoMachinesColor = "black\n";
717         second.twoMachinesColor = "white\n";
718     } else {
719         first.twoMachinesColor = "white\n";
720         second.twoMachinesColor = "black\n";
721     }
722     first.program = appData.firstChessProgram;
723     second.program = appData.secondChessProgram;
724     first.host = appData.firstHost;
725     second.host = appData.secondHost;
726     first.dir = appData.firstDirectory;
727     second.dir = appData.secondDirectory;
728     first.other = &second;
729     second.other = &first;
730     first.initString = appData.initString;
731     second.initString = appData.secondInitString;
732     first.computerString = appData.firstComputerString;
733     second.computerString = appData.secondComputerString;
734     first.useSigint = second.useSigint = TRUE;
735     first.useSigterm = second.useSigterm = TRUE;
736     first.reuse = appData.reuseFirst;
737     second.reuse = appData.reuseSecond;
738     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
739     second.nps = appData.secondNPS;
740     first.useSetboard = second.useSetboard = FALSE;
741     first.useSAN = second.useSAN = FALSE;
742     first.usePing = second.usePing = FALSE;
743     first.lastPing = second.lastPing = 0;
744     first.lastPong = second.lastPong = 0;
745     first.usePlayother = second.usePlayother = FALSE;
746     first.useColors = second.useColors = TRUE;
747     first.useUsermove = second.useUsermove = FALSE;
748     first.sendICS = second.sendICS = FALSE;
749     first.sendName = second.sendName = appData.icsActive;
750     first.sdKludge = second.sdKludge = FALSE;
751     first.stKludge = second.stKludge = FALSE;
752     TidyProgramName(first.program, first.host, first.tidy);
753     TidyProgramName(second.program, second.host, second.tidy);
754     first.matchWins = second.matchWins = 0;
755     strcpy(first.variants, appData.variant);
756     strcpy(second.variants, appData.variant);
757     first.analysisSupport = second.analysisSupport = 2; /* detect */
758     first.analyzing = second.analyzing = FALSE;
759     first.initDone = second.initDone = FALSE;
760
761     /* New features added by Tord: */
762     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764     /* End of new features added by Tord. */
765     first.fenOverride  = appData.fenOverride1;
766     second.fenOverride = appData.fenOverride2;
767
768     /* [HGM] time odds: set factor for each machine */
769     first.timeOdds  = appData.firstTimeOdds;
770     second.timeOdds = appData.secondTimeOdds;
771     { float norm = 1;
772         if(appData.timeOddsMode) {
773             norm = first.timeOdds;
774             if(norm > second.timeOdds) norm = second.timeOdds;
775         }
776         first.timeOdds /= norm;
777         second.timeOdds /= norm;
778     }
779
780     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781     first.accumulateTC = appData.firstAccumulateTC;
782     second.accumulateTC = appData.secondAccumulateTC;
783     first.maxNrOfSessions = second.maxNrOfSessions = 1;
784
785     /* [HGM] debug */
786     first.debug = second.debug = FALSE;
787     first.supportsNPS = second.supportsNPS = UNKNOWN;
788
789     /* [HGM] options */
790     first.optionSettings  = appData.firstOptions;
791     second.optionSettings = appData.secondOptions;
792
793     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795     first.isUCI = appData.firstIsUCI; /* [AS] */
796     second.isUCI = appData.secondIsUCI; /* [AS] */
797     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
799
800     if (appData.firstProtocolVersion > PROTOVER ||
801         appData.firstProtocolVersion < 1) {
802       char buf[MSG_SIZ];
803       sprintf(buf, _("protocol version %d not supported"),
804               appData.firstProtocolVersion);
805       DisplayFatalError(buf, 0, 2);
806     } else {
807       first.protocolVersion = appData.firstProtocolVersion;
808     }
809
810     if (appData.secondProtocolVersion > PROTOVER ||
811         appData.secondProtocolVersion < 1) {
812       char buf[MSG_SIZ];
813       sprintf(buf, _("protocol version %d not supported"),
814               appData.secondProtocolVersion);
815       DisplayFatalError(buf, 0, 2);
816     } else {
817       second.protocolVersion = appData.secondProtocolVersion;
818     }
819
820     if (appData.icsActive) {
821         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
822 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824         appData.clockMode = FALSE;
825         first.sendTime = second.sendTime = 0;
826     }
827     
828 #if ZIPPY
829     /* Override some settings from environment variables, for backward
830        compatibility.  Unfortunately it's not feasible to have the env
831        vars just set defaults, at least in xboard.  Ugh.
832     */
833     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
834       ZippyInit();
835     }
836 #endif
837     
838     if (appData.noChessProgram) {
839         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840         sprintf(programVersion, "%s", PACKAGE_STRING);
841     } else {
842       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
845     }
846
847     if (!appData.icsActive) {
848       char buf[MSG_SIZ];
849       /* Check for variants that are supported only in ICS mode,
850          or not at all.  Some that are accepted here nevertheless
851          have bugs; see comments below.
852       */
853       VariantClass variant = StringToVariant(appData.variant);
854       switch (variant) {
855       case VariantBughouse:     /* need four players and two boards */
856       case VariantKriegspiel:   /* need to hide pieces and move details */
857       /* case VariantFischeRandom: (Fabien: moved below) */
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859         DisplayFatalError(buf, 0, 2);
860         return;
861
862       case VariantUnknown:
863       case VariantLoadable:
864       case Variant29:
865       case Variant30:
866       case Variant31:
867       case Variant32:
868       case Variant33:
869       case Variant34:
870       case Variant35:
871       case Variant36:
872       default:
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);
874         DisplayFatalError(buf, 0, 2);
875         return;
876
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */
879       case VariantGothic:     /* [HGM] should work */
880       case VariantCapablanca: /* [HGM] should work */
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */
882       case VariantShogi:      /* [HGM] drops not tested for legality */
883       case VariantKnightmate: /* [HGM] should work */
884       case VariantCylinder:   /* [HGM] untested */
885       case VariantFalcon:     /* [HGM] untested */
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887                                  offboard interposition not understood */
888       case VariantNormal:     /* definitely works! */
889       case VariantWildCastle: /* pieces not automatically shuffled */
890       case VariantNoCastle:   /* pieces not automatically shuffled */
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892       case VariantLosers:     /* should work except for win condition,
893                                  and doesn't know captures are mandatory */
894       case VariantSuicide:    /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantGiveaway:   /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantTwoKings:   /* should work */
899       case VariantAtomic:     /* should work except for win condition */
900       case Variant3Check:     /* should work except for win condition */
901       case VariantShatranj:   /* should work except for all win conditions */
902       case VariantMakruk:     /* should work except for daw countdown */
903       case VariantBerolina:   /* might work if TestLegality is off */
904       case VariantCapaRandom: /* should work */
905       case VariantJanus:      /* should work */
906       case VariantSuper:      /* experimental */
907       case VariantGreat:      /* experimental, requires legality testing to be off */
908         break;
909       }
910     }
911
912     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
913     InitEngineUCI( installDir, &second );
914 }
915
916 int NextIntegerFromString( char ** str, long * value )
917 {
918     int result = -1;
919     char * s = *str;
920
921     while( *s == ' ' || *s == '\t' ) {
922         s++;
923     }
924
925     *value = 0;
926
927     if( *s >= '0' && *s <= '9' ) {
928         while( *s >= '0' && *s <= '9' ) {
929             *value = *value * 10 + (*s - '0');
930             s++;
931         }
932
933         result = 0;
934     }
935
936     *str = s;
937
938     return result;
939 }
940
941 int NextTimeControlFromString( char ** str, long * value )
942 {
943     long temp;
944     int result = NextIntegerFromString( str, &temp );
945
946     if( result == 0 ) {
947         *value = temp * 60; /* Minutes */
948         if( **str == ':' ) {
949             (*str)++;
950             result = NextIntegerFromString( str, &temp );
951             *value += temp; /* Seconds */
952         }
953     }
954
955     return result;
956 }
957
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
960     int result = -1; long temp, temp2;
961
962     if(**str != '+') return -1; // old params remain in force!
963     (*str)++;
964     if( NextTimeControlFromString( str, &temp ) ) return -1;
965
966     if(**str != '/') {
967         /* time only: incremental or sudden-death time control */
968         if(**str == '+') { /* increment follows; read it */
969             (*str)++;
970             if(result = NextIntegerFromString( str, &temp2)) return -1;
971             *inc = temp2 * 1000;
972         } else *inc = 0;
973         *moves = 0; *tc = temp * 1000; 
974         return 0;
975     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
976
977     (*str)++; /* classical time control */
978     result = NextTimeControlFromString( str, &temp2);
979     if(result == 0) {
980         *moves = temp/60;
981         *tc    = temp2 * 1000;
982         *inc   = 0;
983     }
984     return result;
985 }
986
987 int GetTimeQuota(int movenr)
988 {   /* [HGM] get time to add from the multi-session time-control string */
989     int moves=1; /* kludge to force reading of first session */
990     long time, increment;
991     char *s = fullTimeControlString;
992
993     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994     do {
995         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997         if(movenr == -1) return time;    /* last move before new session     */
998         if(!moves) return increment;     /* current session is incremental   */
999         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000     } while(movenr >= -1);               /* try again for next session       */
1001
1002     return 0; // no new time quota on this move
1003 }
1004
1005 int
1006 ParseTimeControl(tc, ti, mps)
1007      char *tc;
1008      int ti;
1009      int mps;
1010 {
1011   long tc1;
1012   long tc2;
1013   char buf[MSG_SIZ];
1014   
1015   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1016   if(ti > 0) {
1017     if(mps)
1018       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019     else sprintf(buf, "+%s+%d", tc, ti);
1020   } else {
1021     if(mps)
1022              sprintf(buf, "+%d/%s", mps, tc);
1023     else sprintf(buf, "+%s", tc);
1024   }
1025   fullTimeControlString = StrSave(buf);
1026   
1027   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1028     return FALSE;
1029   }
1030   
1031   if( *tc == '/' ) {
1032     /* Parse second time control */
1033     tc++;
1034     
1035     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1036       return FALSE;
1037     }
1038     
1039     if( tc2 == 0 ) {
1040       return FALSE;
1041     }
1042     
1043     timeControl_2 = tc2 * 1000;
1044   }
1045   else {
1046     timeControl_2 = 0;
1047   }
1048   
1049   if( tc1 == 0 ) {
1050     return FALSE;
1051   }
1052   
1053   timeControl = tc1 * 1000;
1054   
1055   if (ti >= 0) {
1056     timeIncrement = ti * 1000;  /* convert to ms */
1057     movesPerSession = 0;
1058   } else {
1059     timeIncrement = 0;
1060     movesPerSession = mps;
1061   }
1062   return TRUE;
1063 }
1064
1065 void
1066 InitBackEnd2()
1067 {
1068     if (appData.debugMode) {
1069         fprintf(debugFP, "%s\n", programVersion);
1070     }
1071
1072     set_cont_sequence(appData.wrapContSeq);
1073     if (appData.matchGames > 0) {
1074         appData.matchMode = TRUE;
1075     } else if (appData.matchMode) {
1076         appData.matchGames = 1;
1077     }
1078     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079         appData.matchGames = appData.sameColorGames;
1080     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1083     }
1084     Reset(TRUE, FALSE);
1085     if (appData.noChessProgram || first.protocolVersion == 1) {
1086       InitBackEnd3();
1087     } else {
1088       /* kludge: allow timeout for initial "feature" commands */
1089       FreezeUI();
1090       DisplayMessage("", _("Starting chess program"));
1091       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1092     }
1093 }
1094
1095 void
1096 InitBackEnd3 P((void))
1097 {
1098     GameMode initialMode;
1099     char buf[MSG_SIZ];
1100     int err;
1101
1102     InitChessProgram(&first, startedFromSetupPosition);
1103
1104
1105     if (appData.icsActive) {
1106 #ifdef WIN32
1107         /* [DM] Make a console window if needed [HGM] merged ifs */
1108         ConsoleCreate(); 
1109 #endif
1110         err = establish();
1111         if (err != 0) {
1112             if (*appData.icsCommPort != NULLCHAR) {
1113                 sprintf(buf, _("Could not open comm port %s"),  
1114                         appData.icsCommPort);
1115             } else {
1116                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1117                         appData.icsHost, appData.icsPort);
1118             }
1119             DisplayFatalError(buf, err, 1);
1120             return;
1121         }
1122         SetICSMode();
1123         telnetISR =
1124           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1125         fromUserISR =
1126           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1127         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1128             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1129     } else if (appData.noChessProgram) {
1130         SetNCPMode();
1131     } else {
1132         SetGNUMode();
1133     }
1134
1135     if (*appData.cmailGameName != NULLCHAR) {
1136         SetCmailMode();
1137         OpenLoopback(&cmailPR);
1138         cmailISR =
1139           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1140     }
1141     
1142     ThawUI();
1143     DisplayMessage("", "");
1144     if (StrCaseCmp(appData.initialMode, "") == 0) {
1145       initialMode = BeginningOfGame;
1146     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1147       initialMode = TwoMachinesPlay;
1148     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1149       initialMode = AnalyzeFile; 
1150     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1151       initialMode = AnalyzeMode;
1152     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1153       initialMode = MachinePlaysWhite;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1155       initialMode = MachinePlaysBlack;
1156     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1157       initialMode = EditGame;
1158     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1159       initialMode = EditPosition;
1160     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1161       initialMode = Training;
1162     } else {
1163       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1164       DisplayFatalError(buf, 0, 2);
1165       return;
1166     }
1167
1168     if (appData.matchMode) {
1169         /* Set up machine vs. machine match */
1170         if (appData.noChessProgram) {
1171             DisplayFatalError(_("Can't have a match with no chess programs"),
1172                               0, 2);
1173             return;
1174         }
1175         matchMode = TRUE;
1176         matchGame = 1;
1177         if (*appData.loadGameFile != NULLCHAR) {
1178             int index = appData.loadGameIndex; // [HGM] autoinc
1179             if(index<0) lastIndex = index = 1;
1180             if (!LoadGameFromFile(appData.loadGameFile,
1181                                   index,
1182                                   appData.loadGameFile, FALSE)) {
1183                 DisplayFatalError(_("Bad game file"), 0, 1);
1184                 return;
1185             }
1186         } else if (*appData.loadPositionFile != NULLCHAR) {
1187             int index = appData.loadPositionIndex; // [HGM] autoinc
1188             if(index<0) lastIndex = index = 1;
1189             if (!LoadPositionFromFile(appData.loadPositionFile,
1190                                       index,
1191                                       appData.loadPositionFile)) {
1192                 DisplayFatalError(_("Bad position file"), 0, 1);
1193                 return;
1194             }
1195         }
1196         TwoMachinesEvent();
1197     } else if (*appData.cmailGameName != NULLCHAR) {
1198         /* Set up cmail mode */
1199         ReloadCmailMsgEvent(TRUE);
1200     } else {
1201         /* Set up other modes */
1202         if (initialMode == AnalyzeFile) {
1203           if (*appData.loadGameFile == NULLCHAR) {
1204             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1205             return;
1206           }
1207         }
1208         if (*appData.loadGameFile != NULLCHAR) {
1209             (void) LoadGameFromFile(appData.loadGameFile,
1210                                     appData.loadGameIndex,
1211                                     appData.loadGameFile, TRUE);
1212         } else if (*appData.loadPositionFile != NULLCHAR) {
1213             (void) LoadPositionFromFile(appData.loadPositionFile,
1214                                         appData.loadPositionIndex,
1215                                         appData.loadPositionFile);
1216             /* [HGM] try to make self-starting even after FEN load */
1217             /* to allow automatic setup of fairy variants with wtm */
1218             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1219                 gameMode = BeginningOfGame;
1220                 setboardSpoiledMachineBlack = 1;
1221             }
1222             /* [HGM] loadPos: make that every new game uses the setup */
1223             /* from file as long as we do not switch variant          */
1224             if(!blackPlaysFirst) {
1225                 startedFromPositionFile = TRUE;
1226                 CopyBoard(filePosition, boards[0]);
1227             }
1228         }
1229         if (initialMode == AnalyzeMode) {
1230           if (appData.noChessProgram) {
1231             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1236             return;
1237           }
1238           AnalyzeModeEvent();
1239         } else if (initialMode == AnalyzeFile) {
1240           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1241           ShowThinkingEvent();
1242           AnalyzeFileEvent();
1243           AnalysisPeriodicEvent(1);
1244         } else if (initialMode == MachinePlaysWhite) {
1245           if (appData.noChessProgram) {
1246             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1247                               0, 2);
1248             return;
1249           }
1250           if (appData.icsActive) {
1251             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1252                               0, 2);
1253             return;
1254           }
1255           MachineWhiteEvent();
1256         } else if (initialMode == MachinePlaysBlack) {
1257           if (appData.noChessProgram) {
1258             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1259                               0, 2);
1260             return;
1261           }
1262           if (appData.icsActive) {
1263             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1264                               0, 2);
1265             return;
1266           }
1267           MachineBlackEvent();
1268         } else if (initialMode == TwoMachinesPlay) {
1269           if (appData.noChessProgram) {
1270             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1271                               0, 2);
1272             return;
1273           }
1274           if (appData.icsActive) {
1275             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1276                               0, 2);
1277             return;
1278           }
1279           TwoMachinesEvent();
1280         } else if (initialMode == EditGame) {
1281           EditGameEvent();
1282         } else if (initialMode == EditPosition) {
1283           EditPositionEvent();
1284         } else if (initialMode == Training) {
1285           if (*appData.loadGameFile == NULLCHAR) {
1286             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1287             return;
1288           }
1289           TrainingEvent();
1290         }
1291     }
1292 }
1293
1294 /*
1295  * Establish will establish a contact to a remote host.port.
1296  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1297  *  used to talk to the host.
1298  * Returns 0 if okay, error code if not.
1299  */
1300 int
1301 establish()
1302 {
1303     char buf[MSG_SIZ];
1304
1305     if (*appData.icsCommPort != NULLCHAR) {
1306         /* Talk to the host through a serial comm port */
1307         return OpenCommPort(appData.icsCommPort, &icsPR);
1308
1309     } else if (*appData.gateway != NULLCHAR) {
1310         if (*appData.remoteShell == NULLCHAR) {
1311             /* Use the rcmd protocol to run telnet program on a gateway host */
1312             snprintf(buf, sizeof(buf), "%s %s %s",
1313                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1314             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1315
1316         } else {
1317             /* Use the rsh program to run telnet program on a gateway host */
1318             if (*appData.remoteUser == NULLCHAR) {
1319                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1320                         appData.gateway, appData.telnetProgram,
1321                         appData.icsHost, appData.icsPort);
1322             } else {
1323                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1324                         appData.remoteShell, appData.gateway, 
1325                         appData.remoteUser, appData.telnetProgram,
1326                         appData.icsHost, appData.icsPort);
1327             }
1328             return StartChildProcess(buf, "", &icsPR);
1329
1330         }
1331     } else if (appData.useTelnet) {
1332         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1333
1334     } else {
1335         /* TCP socket interface differs somewhat between
1336            Unix and NT; handle details in the front end.
1337            */
1338         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1339     }
1340 }
1341
1342 void
1343 show_bytes(fp, buf, count)
1344      FILE *fp;
1345      char *buf;
1346      int count;
1347 {
1348     while (count--) {
1349         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1350             fprintf(fp, "\\%03o", *buf & 0xff);
1351         } else {
1352             putc(*buf, fp);
1353         }
1354         buf++;
1355     }
1356     fflush(fp);
1357 }
1358
1359 /* Returns an errno value */
1360 int
1361 OutputMaybeTelnet(pr, message, count, outError)
1362      ProcRef pr;
1363      char *message;
1364      int count;
1365      int *outError;
1366 {
1367     char buf[8192], *p, *q, *buflim;
1368     int left, newcount, outcount;
1369
1370     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1371         *appData.gateway != NULLCHAR) {
1372         if (appData.debugMode) {
1373             fprintf(debugFP, ">ICS: ");
1374             show_bytes(debugFP, message, count);
1375             fprintf(debugFP, "\n");
1376         }
1377         return OutputToProcess(pr, message, count, outError);
1378     }
1379
1380     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1381     p = message;
1382     q = buf;
1383     left = count;
1384     newcount = 0;
1385     while (left) {
1386         if (q >= buflim) {
1387             if (appData.debugMode) {
1388                 fprintf(debugFP, ">ICS: ");
1389                 show_bytes(debugFP, buf, newcount);
1390                 fprintf(debugFP, "\n");
1391             }
1392             outcount = OutputToProcess(pr, buf, newcount, outError);
1393             if (outcount < newcount) return -1; /* to be sure */
1394             q = buf;
1395             newcount = 0;
1396         }
1397         if (*p == '\n') {
1398             *q++ = '\r';
1399             newcount++;
1400         } else if (((unsigned char) *p) == TN_IAC) {
1401             *q++ = (char) TN_IAC;
1402             newcount ++;
1403         }
1404         *q++ = *p++;
1405         newcount++;
1406         left--;
1407     }
1408     if (appData.debugMode) {
1409         fprintf(debugFP, ">ICS: ");
1410         show_bytes(debugFP, buf, newcount);
1411         fprintf(debugFP, "\n");
1412     }
1413     outcount = OutputToProcess(pr, buf, newcount, outError);
1414     if (outcount < newcount) return -1; /* to be sure */
1415     return count;
1416 }
1417
1418 void
1419 read_from_player(isr, closure, message, count, error)
1420      InputSourceRef isr;
1421      VOIDSTAR closure;
1422      char *message;
1423      int count;
1424      int error;
1425 {
1426     int outError, outCount;
1427     static int gotEof = 0;
1428
1429     /* Pass data read from player on to ICS */
1430     if (count > 0) {
1431         gotEof = 0;
1432         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1433         if (outCount < count) {
1434             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1435         }
1436     } else if (count < 0) {
1437         RemoveInputSource(isr);
1438         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1439     } else if (gotEof++ > 0) {
1440         RemoveInputSource(isr);
1441         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1442     }
1443 }
1444
1445 void
1446 KeepAlive()
1447 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1448     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1449     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1450     SendToICS("date\n");
1451     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1452 }
1453
1454 /* added routine for printf style output to ics */
1455 void ics_printf(char *format, ...)
1456 {
1457     char buffer[MSG_SIZ];
1458     va_list args;
1459
1460     va_start(args, format);
1461     vsnprintf(buffer, sizeof(buffer), format, args);
1462     buffer[sizeof(buffer)-1] = '\0';
1463     SendToICS(buffer);
1464     va_end(args);
1465 }
1466
1467 void
1468 SendToICS(s)
1469      char *s;
1470 {
1471     int count, outCount, outError;
1472
1473     if (icsPR == NULL) return;
1474
1475     count = strlen(s);
1476     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1477     if (outCount < count) {
1478         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1479     }
1480 }
1481
1482 /* This is used for sending logon scripts to the ICS. Sending
1483    without a delay causes problems when using timestamp on ICC
1484    (at least on my machine). */
1485 void
1486 SendToICSDelayed(s,msdelay)
1487      char *s;
1488      long msdelay;
1489 {
1490     int count, outCount, outError;
1491
1492     if (icsPR == NULL) return;
1493
1494     count = strlen(s);
1495     if (appData.debugMode) {
1496         fprintf(debugFP, ">ICS: ");
1497         show_bytes(debugFP, s, count);
1498         fprintf(debugFP, "\n");
1499     }
1500     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1501                                       msdelay);
1502     if (outCount < count) {
1503         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1504     }
1505 }
1506
1507
1508 /* Remove all highlighting escape sequences in s
1509    Also deletes any suffix starting with '(' 
1510    */
1511 char *
1512 StripHighlightAndTitle(s)
1513      char *s;
1514 {
1515     static char retbuf[MSG_SIZ];
1516     char *p = retbuf;
1517
1518     while (*s != NULLCHAR) {
1519         while (*s == '\033') {
1520             while (*s != NULLCHAR && !isalpha(*s)) s++;
1521             if (*s != NULLCHAR) s++;
1522         }
1523         while (*s != NULLCHAR && *s != '\033') {
1524             if (*s == '(' || *s == '[') {
1525                 *p = NULLCHAR;
1526                 return retbuf;
1527             }
1528             *p++ = *s++;
1529         }
1530     }
1531     *p = NULLCHAR;
1532     return retbuf;
1533 }
1534
1535 /* Remove all highlighting escape sequences in s */
1536 char *
1537 StripHighlight(s)
1538      char *s;
1539 {
1540     static char retbuf[MSG_SIZ];
1541     char *p = retbuf;
1542
1543     while (*s != NULLCHAR) {
1544         while (*s == '\033') {
1545             while (*s != NULLCHAR && !isalpha(*s)) s++;
1546             if (*s != NULLCHAR) s++;
1547         }
1548         while (*s != NULLCHAR && *s != '\033') {
1549             *p++ = *s++;
1550         }
1551     }
1552     *p = NULLCHAR;
1553     return retbuf;
1554 }
1555
1556 char *variantNames[] = VARIANT_NAMES;
1557 char *
1558 VariantName(v)
1559      VariantClass v;
1560 {
1561     return variantNames[v];
1562 }
1563
1564
1565 /* Identify a variant from the strings the chess servers use or the
1566    PGN Variant tag names we use. */
1567 VariantClass
1568 StringToVariant(e)
1569      char *e;
1570 {
1571     char *p;
1572     int wnum = -1;
1573     VariantClass v = VariantNormal;
1574     int i, found = FALSE;
1575     char buf[MSG_SIZ];
1576
1577     if (!e) return v;
1578
1579     /* [HGM] skip over optional board-size prefixes */
1580     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1581         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1582         while( *e++ != '_');
1583     }
1584
1585     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1586         v = VariantNormal;
1587         found = TRUE;
1588     } else
1589     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1590       if (StrCaseStr(e, variantNames[i])) {
1591         v = (VariantClass) i;
1592         found = TRUE;
1593         break;
1594       }
1595     }
1596
1597     if (!found) {
1598       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1599           || StrCaseStr(e, "wild/fr") 
1600           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1601         v = VariantFischeRandom;
1602       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1603                  (i = 1, p = StrCaseStr(e, "w"))) {
1604         p += i;
1605         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1606         if (isdigit(*p)) {
1607           wnum = atoi(p);
1608         } else {
1609           wnum = -1;
1610         }
1611         switch (wnum) {
1612         case 0: /* FICS only, actually */
1613         case 1:
1614           /* Castling legal even if K starts on d-file */
1615           v = VariantWildCastle;
1616           break;
1617         case 2:
1618         case 3:
1619         case 4:
1620           /* Castling illegal even if K & R happen to start in
1621              normal positions. */
1622           v = VariantNoCastle;
1623           break;
1624         case 5:
1625         case 7:
1626         case 8:
1627         case 10:
1628         case 11:
1629         case 12:
1630         case 13:
1631         case 14:
1632         case 15:
1633         case 18:
1634         case 19:
1635           /* Castling legal iff K & R start in normal positions */
1636           v = VariantNormal;
1637           break;
1638         case 6:
1639         case 20:
1640         case 21:
1641           /* Special wilds for position setup; unclear what to do here */
1642           v = VariantLoadable;
1643           break;
1644         case 9:
1645           /* Bizarre ICC game */
1646           v = VariantTwoKings;
1647           break;
1648         case 16:
1649           v = VariantKriegspiel;
1650           break;
1651         case 17:
1652           v = VariantLosers;
1653           break;
1654         case 22:
1655           v = VariantFischeRandom;
1656           break;
1657         case 23:
1658           v = VariantCrazyhouse;
1659           break;
1660         case 24:
1661           v = VariantBughouse;
1662           break;
1663         case 25:
1664           v = Variant3Check;
1665           break;
1666         case 26:
1667           /* Not quite the same as FICS suicide! */
1668           v = VariantGiveaway;
1669           break;
1670         case 27:
1671           v = VariantAtomic;
1672           break;
1673         case 28:
1674           v = VariantShatranj;
1675           break;
1676
1677         /* Temporary names for future ICC types.  The name *will* change in 
1678            the next xboard/WinBoard release after ICC defines it. */
1679         case 29:
1680           v = Variant29;
1681           break;
1682         case 30:
1683           v = Variant30;
1684           break;
1685         case 31:
1686           v = Variant31;
1687           break;
1688         case 32:
1689           v = Variant32;
1690           break;
1691         case 33:
1692           v = Variant33;
1693           break;
1694         case 34:
1695           v = Variant34;
1696           break;
1697         case 35:
1698           v = Variant35;
1699           break;
1700         case 36:
1701           v = Variant36;
1702           break;
1703         case 37:
1704           v = VariantShogi;
1705           break;
1706         case 38:
1707           v = VariantXiangqi;
1708           break;
1709         case 39:
1710           v = VariantCourier;
1711           break;
1712         case 40:
1713           v = VariantGothic;
1714           break;
1715         case 41:
1716           v = VariantCapablanca;
1717           break;
1718         case 42:
1719           v = VariantKnightmate;
1720           break;
1721         case 43:
1722           v = VariantFairy;
1723           break;
1724         case 44:
1725           v = VariantCylinder;
1726           break;
1727         case 45:
1728           v = VariantFalcon;
1729           break;
1730         case 46:
1731           v = VariantCapaRandom;
1732           break;
1733         case 47:
1734           v = VariantBerolina;
1735           break;
1736         case 48:
1737           v = VariantJanus;
1738           break;
1739         case 49:
1740           v = VariantSuper;
1741           break;
1742         case 50:
1743           v = VariantGreat;
1744           break;
1745         case -1:
1746           /* Found "wild" or "w" in the string but no number;
1747              must assume it's normal chess. */
1748           v = VariantNormal;
1749           break;
1750         default:
1751           sprintf(buf, _("Unknown wild type %d"), wnum);
1752           DisplayError(buf, 0);
1753           v = VariantUnknown;
1754           break;
1755         }
1756       }
1757     }
1758     if (appData.debugMode) {
1759       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1760               e, wnum, VariantName(v));
1761     }
1762     return v;
1763 }
1764
1765 static int leftover_start = 0, leftover_len = 0;
1766 char star_match[STAR_MATCH_N][MSG_SIZ];
1767
1768 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1769    advance *index beyond it, and set leftover_start to the new value of
1770    *index; else return FALSE.  If pattern contains the character '*', it
1771    matches any sequence of characters not containing '\r', '\n', or the
1772    character following the '*' (if any), and the matched sequence(s) are
1773    copied into star_match.
1774    */
1775 int
1776 looking_at(buf, index, pattern)
1777      char *buf;
1778      int *index;
1779      char *pattern;
1780 {
1781     char *bufp = &buf[*index], *patternp = pattern;
1782     int star_count = 0;
1783     char *matchp = star_match[0];
1784     
1785     for (;;) {
1786         if (*patternp == NULLCHAR) {
1787             *index = leftover_start = bufp - buf;
1788             *matchp = NULLCHAR;
1789             return TRUE;
1790         }
1791         if (*bufp == NULLCHAR) return FALSE;
1792         if (*patternp == '*') {
1793             if (*bufp == *(patternp + 1)) {
1794                 *matchp = NULLCHAR;
1795                 matchp = star_match[++star_count];
1796                 patternp += 2;
1797                 bufp++;
1798                 continue;
1799             } else if (*bufp == '\n' || *bufp == '\r') {
1800                 patternp++;
1801                 if (*patternp == NULLCHAR)
1802                   continue;
1803                 else
1804                   return FALSE;
1805             } else {
1806                 *matchp++ = *bufp++;
1807                 continue;
1808             }
1809         }
1810         if (*patternp != *bufp) return FALSE;
1811         patternp++;
1812         bufp++;
1813     }
1814 }
1815
1816 void
1817 SendToPlayer(data, length)
1818      char *data;
1819      int length;
1820 {
1821     int error, outCount;
1822     outCount = OutputToProcess(NoProc, data, length, &error);
1823     if (outCount < length) {
1824         DisplayFatalError(_("Error writing to display"), error, 1);
1825     }
1826 }
1827
1828 void
1829 PackHolding(packed, holding)
1830      char packed[];
1831      char *holding;
1832 {
1833     char *p = holding;
1834     char *q = packed;
1835     int runlength = 0;
1836     int curr = 9999;
1837     do {
1838         if (*p == curr) {
1839             runlength++;
1840         } else {
1841             switch (runlength) {
1842               case 0:
1843                 break;
1844               case 1:
1845                 *q++ = curr;
1846                 break;
1847               case 2:
1848                 *q++ = curr;
1849                 *q++ = curr;
1850                 break;
1851               default:
1852                 sprintf(q, "%d", runlength);
1853                 while (*q) q++;
1854                 *q++ = curr;
1855                 break;
1856             }
1857             runlength = 1;
1858             curr = *p;
1859         }
1860     } while (*p++);
1861     *q = NULLCHAR;
1862 }
1863
1864 /* Telnet protocol requests from the front end */
1865 void
1866 TelnetRequest(ddww, option)
1867      unsigned char ddww, option;
1868 {
1869     unsigned char msg[3];
1870     int outCount, outError;
1871
1872     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1873
1874     if (appData.debugMode) {
1875         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1876         switch (ddww) {
1877           case TN_DO:
1878             ddwwStr = "DO";
1879             break;
1880           case TN_DONT:
1881             ddwwStr = "DONT";
1882             break;
1883           case TN_WILL:
1884             ddwwStr = "WILL";
1885             break;
1886           case TN_WONT:
1887             ddwwStr = "WONT";
1888             break;
1889           default:
1890             ddwwStr = buf1;
1891             sprintf(buf1, "%d", ddww);
1892             break;
1893         }
1894         switch (option) {
1895           case TN_ECHO:
1896             optionStr = "ECHO";
1897             break;
1898           default:
1899             optionStr = buf2;
1900             sprintf(buf2, "%d", option);
1901             break;
1902         }
1903         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1904     }
1905     msg[0] = TN_IAC;
1906     msg[1] = ddww;
1907     msg[2] = option;
1908     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1909     if (outCount < 3) {
1910         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911     }
1912 }
1913
1914 void
1915 DoEcho()
1916 {
1917     if (!appData.icsActive) return;
1918     TelnetRequest(TN_DO, TN_ECHO);
1919 }
1920
1921 void
1922 DontEcho()
1923 {
1924     if (!appData.icsActive) return;
1925     TelnetRequest(TN_DONT, TN_ECHO);
1926 }
1927
1928 void
1929 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1930 {
1931     /* put the holdings sent to us by the server on the board holdings area */
1932     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1933     char p;
1934     ChessSquare piece;
1935
1936     if(gameInfo.holdingsWidth < 2)  return;
1937     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1938         return; // prevent overwriting by pre-board holdings
1939
1940     if( (int)lowestPiece >= BlackPawn ) {
1941         holdingsColumn = 0;
1942         countsColumn = 1;
1943         holdingsStartRow = BOARD_HEIGHT-1;
1944         direction = -1;
1945     } else {
1946         holdingsColumn = BOARD_WIDTH-1;
1947         countsColumn = BOARD_WIDTH-2;
1948         holdingsStartRow = 0;
1949         direction = 1;
1950     }
1951
1952     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1953         board[i][holdingsColumn] = EmptySquare;
1954         board[i][countsColumn]   = (ChessSquare) 0;
1955     }
1956     while( (p=*holdings++) != NULLCHAR ) {
1957         piece = CharToPiece( ToUpper(p) );
1958         if(piece == EmptySquare) continue;
1959         /*j = (int) piece - (int) WhitePawn;*/
1960         j = PieceToNumber(piece);
1961         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1962         if(j < 0) continue;               /* should not happen */
1963         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1964         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1965         board[holdingsStartRow+j*direction][countsColumn]++;
1966     }
1967 }
1968
1969
1970 void
1971 VariantSwitch(Board board, VariantClass newVariant)
1972 {
1973    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1974    Board oldBoard;
1975
1976    startedFromPositionFile = FALSE;
1977    if(gameInfo.variant == newVariant) return;
1978
1979    /* [HGM] This routine is called each time an assignment is made to
1980     * gameInfo.variant during a game, to make sure the board sizes
1981     * are set to match the new variant. If that means adding or deleting
1982     * holdings, we shift the playing board accordingly
1983     * This kludge is needed because in ICS observe mode, we get boards
1984     * of an ongoing game without knowing the variant, and learn about the
1985     * latter only later. This can be because of the move list we requested,
1986     * in which case the game history is refilled from the beginning anyway,
1987     * but also when receiving holdings of a crazyhouse game. In the latter
1988     * case we want to add those holdings to the already received position.
1989     */
1990
1991    
1992    if (appData.debugMode) {
1993      fprintf(debugFP, "Switch board from %s to %s\n",
1994              VariantName(gameInfo.variant), VariantName(newVariant));
1995      setbuf(debugFP, NULL);
1996    }
1997    shuffleOpenings = 0;       /* [HGM] shuffle */
1998    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1999    switch(newVariant) 
2000      {
2001      case VariantShogi:
2002        newWidth = 9;  newHeight = 9;
2003        gameInfo.holdingsSize = 7;
2004      case VariantBughouse:
2005      case VariantCrazyhouse:
2006        newHoldingsWidth = 2; break;
2007      case VariantGreat:
2008        newWidth = 10;
2009      case VariantSuper:
2010        newHoldingsWidth = 2;
2011        gameInfo.holdingsSize = 8;
2012        break;
2013      case VariantGothic:
2014      case VariantCapablanca:
2015      case VariantCapaRandom:
2016        newWidth = 10;
2017      default:
2018        newHoldingsWidth = gameInfo.holdingsSize = 0;
2019      };
2020    
2021    if(newWidth  != gameInfo.boardWidth  ||
2022       newHeight != gameInfo.boardHeight ||
2023       newHoldingsWidth != gameInfo.holdingsWidth ) {
2024      
2025      /* shift position to new playing area, if needed */
2026      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2027        for(i=0; i<BOARD_HEIGHT; i++) 
2028          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2029            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2030              board[i][j];
2031        for(i=0; i<newHeight; i++) {
2032          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2033          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2034        }
2035      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2036        for(i=0; i<BOARD_HEIGHT; i++)
2037          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2038            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2039              board[i][j];
2040      }
2041      gameInfo.boardWidth  = newWidth;
2042      gameInfo.boardHeight = newHeight;
2043      gameInfo.holdingsWidth = newHoldingsWidth;
2044      gameInfo.variant = newVariant;
2045      InitDrawingSizes(-2, 0);
2046    } else gameInfo.variant = newVariant;
2047    CopyBoard(oldBoard, board);   // remember correctly formatted board
2048      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2049    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2050 }
2051
2052 static int loggedOn = FALSE;
2053
2054 /*-- Game start info cache: --*/
2055 int gs_gamenum;
2056 char gs_kind[MSG_SIZ];
2057 static char player1Name[128] = "";
2058 static char player2Name[128] = "";
2059 static char cont_seq[] = "\n\\   ";
2060 static int player1Rating = -1;
2061 static int player2Rating = -1;
2062 /*----------------------------*/
2063
2064 ColorClass curColor = ColorNormal;
2065 int suppressKibitz = 0;
2066
2067 void
2068 read_from_ics(isr, closure, data, count, error)
2069      InputSourceRef isr;
2070      VOIDSTAR closure;
2071      char *data;
2072      int count;
2073      int error;
2074 {
2075 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2076 #define STARTED_NONE 0
2077 #define STARTED_MOVES 1
2078 #define STARTED_BOARD 2
2079 #define STARTED_OBSERVE 3
2080 #define STARTED_HOLDINGS 4
2081 #define STARTED_CHATTER 5
2082 #define STARTED_COMMENT 6
2083 #define STARTED_MOVES_NOHIDE 7
2084     
2085     static int started = STARTED_NONE;
2086     static char parse[20000];
2087     static int parse_pos = 0;
2088     static char buf[BUF_SIZE + 1];
2089     static int firstTime = TRUE, intfSet = FALSE;
2090     static ColorClass prevColor = ColorNormal;
2091     static int savingComment = FALSE;
2092     static int cmatch = 0; // continuation sequence match
2093     char *bp;
2094     char str[500];
2095     int i, oldi;
2096     int buf_len;
2097     int next_out;
2098     int tkind;
2099     int backup;    /* [DM] For zippy color lines */
2100     char *p;
2101     char talker[MSG_SIZ]; // [HGM] chat
2102     int channel;
2103
2104     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2105
2106     if (appData.debugMode) {
2107       if (!error) {
2108         fprintf(debugFP, "<ICS: ");
2109         show_bytes(debugFP, data, count);
2110         fprintf(debugFP, "\n");
2111       }
2112     }
2113
2114     if (appData.debugMode) { int f = forwardMostMove;
2115         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2116                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2117                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2118     }
2119     if (count > 0) {
2120         /* If last read ended with a partial line that we couldn't parse,
2121            prepend it to the new read and try again. */
2122         if (leftover_len > 0) {
2123             for (i=0; i<leftover_len; i++)
2124               buf[i] = buf[leftover_start + i];
2125         }
2126
2127     /* copy new characters into the buffer */
2128     bp = buf + leftover_len;
2129     buf_len=leftover_len;
2130     for (i=0; i<count; i++)
2131     {
2132         // ignore these
2133         if (data[i] == '\r')
2134             continue;
2135
2136         // join lines split by ICS?
2137         if (!appData.noJoin)
2138         {
2139             /*
2140                 Joining just consists of finding matches against the
2141                 continuation sequence, and discarding that sequence
2142                 if found instead of copying it.  So, until a match
2143                 fails, there's nothing to do since it might be the
2144                 complete sequence, and thus, something we don't want
2145                 copied.
2146             */
2147             if (data[i] == cont_seq[cmatch])
2148             {
2149                 cmatch++;
2150                 if (cmatch == strlen(cont_seq))
2151                 {
2152                     cmatch = 0; // complete match.  just reset the counter
2153
2154                     /*
2155                         it's possible for the ICS to not include the space
2156                         at the end of the last word, making our [correct]
2157                         join operation fuse two separate words.  the server
2158                         does this when the space occurs at the width setting.
2159                     */
2160                     if (!buf_len || buf[buf_len-1] != ' ')
2161                     {
2162                         *bp++ = ' ';
2163                         buf_len++;
2164                     }
2165                 }
2166                 continue;
2167             }
2168             else if (cmatch)
2169             {
2170                 /*
2171                     match failed, so we have to copy what matched before
2172                     falling through and copying this character.  In reality,
2173                     this will only ever be just the newline character, but
2174                     it doesn't hurt to be precise.
2175                 */
2176                 strncpy(bp, cont_seq, cmatch);
2177                 bp += cmatch;
2178                 buf_len += cmatch;
2179                 cmatch = 0;
2180             }
2181         }
2182
2183         // copy this char
2184         *bp++ = data[i];
2185         buf_len++;
2186     }
2187
2188         buf[buf_len] = NULLCHAR;
2189 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2190         next_out = 0;
2191         leftover_start = 0;
2192         
2193         i = 0;
2194         while (i < buf_len) {
2195             /* Deal with part of the TELNET option negotiation
2196                protocol.  We refuse to do anything beyond the
2197                defaults, except that we allow the WILL ECHO option,
2198                which ICS uses to turn off password echoing when we are
2199                directly connected to it.  We reject this option
2200                if localLineEditing mode is on (always on in xboard)
2201                and we are talking to port 23, which might be a real
2202                telnet server that will try to keep WILL ECHO on permanently.
2203              */
2204             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2205                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2206                 unsigned char option;
2207                 oldi = i;
2208                 switch ((unsigned char) buf[++i]) {
2209                   case TN_WILL:
2210                     if (appData.debugMode)
2211                       fprintf(debugFP, "\n<WILL ");
2212                     switch (option = (unsigned char) buf[++i]) {
2213                       case TN_ECHO:
2214                         if (appData.debugMode)
2215                           fprintf(debugFP, "ECHO ");
2216                         /* Reply only if this is a change, according
2217                            to the protocol rules. */
2218                         if (remoteEchoOption) break;
2219                         if (appData.localLineEditing &&
2220                             atoi(appData.icsPort) == TN_PORT) {
2221                             TelnetRequest(TN_DONT, TN_ECHO);
2222                         } else {
2223                             EchoOff();
2224                             TelnetRequest(TN_DO, TN_ECHO);
2225                             remoteEchoOption = TRUE;
2226                         }
2227                         break;
2228                       default:
2229                         if (appData.debugMode)
2230                           fprintf(debugFP, "%d ", option);
2231                         /* Whatever this is, we don't want it. */
2232                         TelnetRequest(TN_DONT, option);
2233                         break;
2234                     }
2235                     break;
2236                   case TN_WONT:
2237                     if (appData.debugMode)
2238                       fprintf(debugFP, "\n<WONT ");
2239                     switch (option = (unsigned char) buf[++i]) {
2240                       case TN_ECHO:
2241                         if (appData.debugMode)
2242                           fprintf(debugFP, "ECHO ");
2243                         /* Reply only if this is a change, according
2244                            to the protocol rules. */
2245                         if (!remoteEchoOption) break;
2246                         EchoOn();
2247                         TelnetRequest(TN_DONT, TN_ECHO);
2248                         remoteEchoOption = FALSE;
2249                         break;
2250                       default:
2251                         if (appData.debugMode)
2252                           fprintf(debugFP, "%d ", (unsigned char) option);
2253                         /* Whatever this is, it must already be turned
2254                            off, because we never agree to turn on
2255                            anything non-default, so according to the
2256                            protocol rules, we don't reply. */
2257                         break;
2258                     }
2259                     break;
2260                   case TN_DO:
2261                     if (appData.debugMode)
2262                       fprintf(debugFP, "\n<DO ");
2263                     switch (option = (unsigned char) buf[++i]) {
2264                       default:
2265                         /* Whatever this is, we refuse to do it. */
2266                         if (appData.debugMode)
2267                           fprintf(debugFP, "%d ", option);
2268                         TelnetRequest(TN_WONT, option);
2269                         break;
2270                     }
2271                     break;
2272                   case TN_DONT:
2273                     if (appData.debugMode)
2274                       fprintf(debugFP, "\n<DONT ");
2275                     switch (option = (unsigned char) buf[++i]) {
2276                       default:
2277                         if (appData.debugMode)
2278                           fprintf(debugFP, "%d ", option);
2279                         /* Whatever this is, we are already not doing
2280                            it, because we never agree to do anything
2281                            non-default, so according to the protocol
2282                            rules, we don't reply. */
2283                         break;
2284                     }
2285                     break;
2286                   case TN_IAC:
2287                     if (appData.debugMode)
2288                       fprintf(debugFP, "\n<IAC ");
2289                     /* Doubled IAC; pass it through */
2290                     i--;
2291                     break;
2292                   default:
2293                     if (appData.debugMode)
2294                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2295                     /* Drop all other telnet commands on the floor */
2296                     break;
2297                 }
2298                 if (oldi > next_out)
2299                   SendToPlayer(&buf[next_out], oldi - next_out);
2300                 if (++i > next_out)
2301                   next_out = i;
2302                 continue;
2303             }
2304                 
2305             /* OK, this at least will *usually* work */
2306             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2307                 loggedOn = TRUE;
2308             }
2309             
2310             if (loggedOn && !intfSet) {
2311                 if (ics_type == ICS_ICC) {
2312                   sprintf(str,
2313                           "/set-quietly interface %s\n/set-quietly style 12\n",
2314                           programVersion);
2315                 } else if (ics_type == ICS_CHESSNET) {
2316                   sprintf(str, "/style 12\n");
2317                 } else {
2318                   strcpy(str, "alias $ @\n$set interface ");
2319                   strcat(str, programVersion);
2320                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2321 #ifdef WIN32
2322                   strcat(str, "$iset nohighlight 1\n");
2323 #endif
2324                   strcat(str, "$iset lock 1\n$style 12\n");
2325                 }
2326                 SendToICS(str);
2327                 NotifyFrontendLogin();
2328                 intfSet = TRUE;
2329             }
2330
2331             if (started == STARTED_COMMENT) {
2332                 /* Accumulate characters in comment */
2333                 parse[parse_pos++] = buf[i];
2334                 if (buf[i] == '\n') {
2335                     parse[parse_pos] = NULLCHAR;
2336                     if(chattingPartner>=0) {
2337                         char mess[MSG_SIZ];
2338                         sprintf(mess, "%s%s", talker, parse);
2339                         OutputChatMessage(chattingPartner, mess);
2340                         chattingPartner = -1;
2341                     } else
2342                     if(!suppressKibitz) // [HGM] kibitz
2343                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2344                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2345                         int nrDigit = 0, nrAlph = 0, j;
2346                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2347                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2348                         parse[parse_pos] = NULLCHAR;
2349                         // try to be smart: if it does not look like search info, it should go to
2350                         // ICS interaction window after all, not to engine-output window.
2351                         for(j=0; j<parse_pos; j++) { // count letters and digits
2352                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2353                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2354                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2355                         }
2356                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2357                             int depth=0; float score;
2358                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2359                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2360                                 pvInfoList[forwardMostMove-1].depth = depth;
2361                                 pvInfoList[forwardMostMove-1].score = 100*score;
2362                             }
2363                             OutputKibitz(suppressKibitz, parse);
2364                             next_out = i+1; // [HGM] suppress printing in ICS window
2365                         } else {
2366                             char tmp[MSG_SIZ];
2367                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2368                             SendToPlayer(tmp, strlen(tmp));
2369                         }
2370                     }
2371                     started = STARTED_NONE;
2372                 } else {
2373                     /* Don't match patterns against characters in comment */
2374                     i++;
2375                     continue;
2376                 }
2377             }
2378             if (started == STARTED_CHATTER) {
2379                 if (buf[i] != '\n') {
2380                     /* Don't match patterns against characters in chatter */
2381                     i++;
2382                     continue;
2383                 }
2384                 started = STARTED_NONE;
2385             }
2386
2387             /* Kludge to deal with rcmd protocol */
2388             if (firstTime && looking_at(buf, &i, "\001*")) {
2389                 DisplayFatalError(&buf[1], 0, 1);
2390                 continue;
2391             } else {
2392                 firstTime = FALSE;
2393             }
2394
2395             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2396                 ics_type = ICS_ICC;
2397                 ics_prefix = "/";
2398                 if (appData.debugMode)
2399                   fprintf(debugFP, "ics_type %d\n", ics_type);
2400                 continue;
2401             }
2402             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2403                 ics_type = ICS_FICS;
2404                 ics_prefix = "$";
2405                 if (appData.debugMode)
2406                   fprintf(debugFP, "ics_type %d\n", ics_type);
2407                 continue;
2408             }
2409             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2410                 ics_type = ICS_CHESSNET;
2411                 ics_prefix = "/";
2412                 if (appData.debugMode)
2413                   fprintf(debugFP, "ics_type %d\n", ics_type);
2414                 continue;
2415             }
2416
2417             if (!loggedOn &&
2418                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2419                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2420                  looking_at(buf, &i, "will be \"*\""))) {
2421               strcpy(ics_handle, star_match[0]);
2422               continue;
2423             }
2424
2425             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2426               char buf[MSG_SIZ];
2427               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2428               DisplayIcsInteractionTitle(buf);
2429               have_set_title = TRUE;
2430             }
2431
2432             /* skip finger notes */
2433             if (started == STARTED_NONE &&
2434                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2435                  (buf[i] == '1' && buf[i+1] == '0')) &&
2436                 buf[i+2] == ':' && buf[i+3] == ' ') {
2437               started = STARTED_CHATTER;
2438               i += 3;
2439               continue;
2440             }
2441
2442             /* skip formula vars */
2443             if (started == STARTED_NONE &&
2444                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2445               started = STARTED_CHATTER;
2446               i += 3;
2447               continue;
2448             }
2449
2450             oldi = i;
2451             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2452             if (appData.autoKibitz && started == STARTED_NONE && 
2453                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2454                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2455                 if(looking_at(buf, &i, "* kibitzes: ") &&
2456                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2457                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2458                         suppressKibitz = TRUE;
2459                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2460                                 && (gameMode == IcsPlayingWhite)) ||
2461                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2462                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2463                             started = STARTED_CHATTER; // own kibitz we simply discard
2464                         else {
2465                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2466                             parse_pos = 0; parse[0] = NULLCHAR;
2467                             savingComment = TRUE;
2468                             suppressKibitz = gameMode != IcsObserving ? 2 :
2469                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2470                         } 
2471                         continue;
2472                 } else
2473                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2474                     // suppress the acknowledgements of our own autoKibitz
2475                     SendToPlayer(star_match[0], strlen(star_match[0]));
2476                     looking_at(buf, &i, "*% "); // eat prompt
2477                     next_out = i;
2478                 }
2479             } // [HGM] kibitz: end of patch
2480
2481 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2482
2483             // [HGM] chat: intercept tells by users for which we have an open chat window
2484             channel = -1;
2485             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2486                                            looking_at(buf, &i, "* whispers:") ||
2487                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2488                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2489                 int p;
2490                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2491                 chattingPartner = -1;
2492
2493                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2494                 for(p=0; p<MAX_CHAT; p++) {
2495                     if(channel == atoi(chatPartner[p])) {
2496                     talker[0] = '['; strcat(talker, "] ");
2497                     chattingPartner = p; break;
2498                     }
2499                 } else
2500                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2501                 for(p=0; p<MAX_CHAT; p++) {
2502                     if(!strcmp("WHISPER", chatPartner[p])) {
2503                         talker[0] = '['; strcat(talker, "] ");
2504                         chattingPartner = p; break;
2505                     }
2506                 }
2507                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2508                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2509                     talker[0] = 0;
2510                     chattingPartner = p; break;
2511                 }
2512                 if(chattingPartner<0) i = oldi; else {
2513                     started = STARTED_COMMENT;
2514                     parse_pos = 0; parse[0] = NULLCHAR;
2515                     savingComment = 3 + chattingPartner; // counts as TRUE
2516                     suppressKibitz = TRUE;
2517                 }
2518             } // [HGM] chat: end of patch
2519
2520             if (appData.zippyTalk || appData.zippyPlay) {
2521                 /* [DM] Backup address for color zippy lines */
2522                 backup = i;
2523 #if ZIPPY
2524        #ifdef WIN32
2525                if (loggedOn == TRUE)
2526                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2527                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2528        #else
2529                 if (ZippyControl(buf, &i) ||
2530                     ZippyConverse(buf, &i) ||
2531                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2532                       loggedOn = TRUE;
2533                       if (!appData.colorize) continue;
2534                 }
2535        #endif
2536 #endif
2537             } // [DM] 'else { ' deleted
2538                 if (
2539                     /* Regular tells and says */
2540                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2541                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2542                     looking_at(buf, &i, "* says: ") ||
2543                     /* Don't color "message" or "messages" output */
2544                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2545                     looking_at(buf, &i, "*. * at *:*: ") ||
2546                     looking_at(buf, &i, "--* (*:*): ") ||
2547                     /* Message notifications (same color as tells) */
2548                     looking_at(buf, &i, "* has left a message ") ||
2549                     looking_at(buf, &i, "* just sent you a message:\n") ||
2550                     /* Whispers and kibitzes */
2551                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2552                     looking_at(buf, &i, "* kibitzes: ") ||
2553                     /* Channel tells */
2554                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2555
2556                   if (tkind == 1 && strchr(star_match[0], ':')) {
2557                       /* Avoid "tells you:" spoofs in channels */
2558                      tkind = 3;
2559                   }
2560                   if (star_match[0][0] == NULLCHAR ||
2561                       strchr(star_match[0], ' ') ||
2562                       (tkind == 3 && strchr(star_match[1], ' '))) {
2563                     /* Reject bogus matches */
2564                     i = oldi;
2565                   } else {
2566                     if (appData.colorize) {
2567                       if (oldi > next_out) {
2568                         SendToPlayer(&buf[next_out], oldi - next_out);
2569                         next_out = oldi;
2570                       }
2571                       switch (tkind) {
2572                       case 1:
2573                         Colorize(ColorTell, FALSE);
2574                         curColor = ColorTell;
2575                         break;
2576                       case 2:
2577                         Colorize(ColorKibitz, FALSE);
2578                         curColor = ColorKibitz;
2579                         break;
2580                       case 3:
2581                         p = strrchr(star_match[1], '(');
2582                         if (p == NULL) {
2583                           p = star_match[1];
2584                         } else {
2585                           p++;
2586                         }
2587                         if (atoi(p) == 1) {
2588                           Colorize(ColorChannel1, FALSE);
2589                           curColor = ColorChannel1;
2590                         } else {
2591                           Colorize(ColorChannel, FALSE);
2592                           curColor = ColorChannel;
2593                         }
2594                         break;
2595                       case 5:
2596                         curColor = ColorNormal;
2597                         break;
2598                       }
2599                     }
2600                     if (started == STARTED_NONE && appData.autoComment &&
2601                         (gameMode == IcsObserving ||
2602                          gameMode == IcsPlayingWhite ||
2603                          gameMode == IcsPlayingBlack)) {
2604                       parse_pos = i - oldi;
2605                       memcpy(parse, &buf[oldi], parse_pos);
2606                       parse[parse_pos] = NULLCHAR;
2607                       started = STARTED_COMMENT;
2608                       savingComment = TRUE;
2609                     } else {
2610                       started = STARTED_CHATTER;
2611                       savingComment = FALSE;
2612                     }
2613                     loggedOn = TRUE;
2614                     continue;
2615                   }
2616                 }
2617
2618                 if (looking_at(buf, &i, "* s-shouts: ") ||
2619                     looking_at(buf, &i, "* c-shouts: ")) {
2620                     if (appData.colorize) {
2621                         if (oldi > next_out) {
2622                             SendToPlayer(&buf[next_out], oldi - next_out);
2623                             next_out = oldi;
2624                         }
2625                         Colorize(ColorSShout, FALSE);
2626                         curColor = ColorSShout;
2627                     }
2628                     loggedOn = TRUE;
2629                     started = STARTED_CHATTER;
2630                     continue;
2631                 }
2632
2633                 if (looking_at(buf, &i, "--->")) {
2634                     loggedOn = TRUE;
2635                     continue;
2636                 }
2637
2638                 if (looking_at(buf, &i, "* shouts: ") ||
2639                     looking_at(buf, &i, "--> ")) {
2640                     if (appData.colorize) {
2641                         if (oldi > next_out) {
2642                             SendToPlayer(&buf[next_out], oldi - next_out);
2643                             next_out = oldi;
2644                         }
2645                         Colorize(ColorShout, FALSE);
2646                         curColor = ColorShout;
2647                     }
2648                     loggedOn = TRUE;
2649                     started = STARTED_CHATTER;
2650                     continue;
2651                 }
2652
2653                 if (looking_at( buf, &i, "Challenge:")) {
2654                     if (appData.colorize) {
2655                         if (oldi > next_out) {
2656                             SendToPlayer(&buf[next_out], oldi - next_out);
2657                             next_out = oldi;
2658                         }
2659                         Colorize(ColorChallenge, FALSE);
2660                         curColor = ColorChallenge;
2661                     }
2662                     loggedOn = TRUE;
2663                     continue;
2664                 }
2665
2666                 if (looking_at(buf, &i, "* offers you") ||
2667                     looking_at(buf, &i, "* offers to be") ||
2668                     looking_at(buf, &i, "* would like to") ||
2669                     looking_at(buf, &i, "* requests to") ||
2670                     looking_at(buf, &i, "Your opponent offers") ||
2671                     looking_at(buf, &i, "Your opponent requests")) {
2672
2673                     if (appData.colorize) {
2674                         if (oldi > next_out) {
2675                             SendToPlayer(&buf[next_out], oldi - next_out);
2676                             next_out = oldi;
2677                         }
2678                         Colorize(ColorRequest, FALSE);
2679                         curColor = ColorRequest;
2680                     }
2681                     continue;
2682                 }
2683
2684                 if (looking_at(buf, &i, "* (*) seeking")) {
2685                     if (appData.colorize) {
2686                         if (oldi > next_out) {
2687                             SendToPlayer(&buf[next_out], oldi - next_out);
2688                             next_out = oldi;
2689                         }
2690                         Colorize(ColorSeek, FALSE);
2691                         curColor = ColorSeek;
2692                     }
2693                     continue;
2694             }
2695
2696             if (looking_at(buf, &i, "\\   ")) {
2697                 if (prevColor != ColorNormal) {
2698                     if (oldi > next_out) {
2699                         SendToPlayer(&buf[next_out], oldi - next_out);
2700                         next_out = oldi;
2701                     }
2702                     Colorize(prevColor, TRUE);
2703                     curColor = prevColor;
2704                 }
2705                 if (savingComment) {
2706                     parse_pos = i - oldi;
2707                     memcpy(parse, &buf[oldi], parse_pos);
2708                     parse[parse_pos] = NULLCHAR;
2709                     started = STARTED_COMMENT;
2710                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2711                         chattingPartner = savingComment - 3; // kludge to remember the box
2712                 } else {
2713                     started = STARTED_CHATTER;
2714                 }
2715                 continue;
2716             }
2717
2718             if (looking_at(buf, &i, "Black Strength :") ||
2719                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2720                 looking_at(buf, &i, "<10>") ||
2721                 looking_at(buf, &i, "#@#")) {
2722                 /* Wrong board style */
2723                 loggedOn = TRUE;
2724                 SendToICS(ics_prefix);
2725                 SendToICS("set style 12\n");
2726                 SendToICS(ics_prefix);
2727                 SendToICS("refresh\n");
2728                 continue;
2729             }
2730             
2731             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2732                 ICSInitScript();
2733                 have_sent_ICS_logon = 1;
2734                 continue;
2735             }
2736               
2737             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2738                 (looking_at(buf, &i, "\n<12> ") ||
2739                  looking_at(buf, &i, "<12> "))) {
2740                 loggedOn = TRUE;
2741                 if (oldi > next_out) {
2742                     SendToPlayer(&buf[next_out], oldi - next_out);
2743                 }
2744                 next_out = i;
2745                 started = STARTED_BOARD;
2746                 parse_pos = 0;
2747                 continue;
2748             }
2749
2750             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2751                 looking_at(buf, &i, "<b1> ")) {
2752                 if (oldi > next_out) {
2753                     SendToPlayer(&buf[next_out], oldi - next_out);
2754                 }
2755                 next_out = i;
2756                 started = STARTED_HOLDINGS;
2757                 parse_pos = 0;
2758                 continue;
2759             }
2760
2761             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2762                 loggedOn = TRUE;
2763                 /* Header for a move list -- first line */
2764
2765                 switch (ics_getting_history) {
2766                   case H_FALSE:
2767                     switch (gameMode) {
2768                       case IcsIdle:
2769                       case BeginningOfGame:
2770                         /* User typed "moves" or "oldmoves" while we
2771                            were idle.  Pretend we asked for these
2772                            moves and soak them up so user can step
2773                            through them and/or save them.
2774                            */
2775                         Reset(FALSE, TRUE);
2776                         gameMode = IcsObserving;
2777                         ModeHighlight();
2778                         ics_gamenum = -1;
2779                         ics_getting_history = H_GOT_UNREQ_HEADER;
2780                         break;
2781                       case EditGame: /*?*/
2782                       case EditPosition: /*?*/
2783                         /* Should above feature work in these modes too? */
2784                         /* For now it doesn't */
2785                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2786                         break;
2787                       default:
2788                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2789                         break;
2790                     }
2791                     break;
2792                   case H_REQUESTED:
2793                     /* Is this the right one? */
2794                     if (gameInfo.white && gameInfo.black &&
2795                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2796                         strcmp(gameInfo.black, star_match[2]) == 0) {
2797                         /* All is well */
2798                         ics_getting_history = H_GOT_REQ_HEADER;
2799                     }
2800                     break;
2801                   case H_GOT_REQ_HEADER:
2802                   case H_GOT_UNREQ_HEADER:
2803                   case H_GOT_UNWANTED_HEADER:
2804                   case H_GETTING_MOVES:
2805                     /* Should not happen */
2806                     DisplayError(_("Error gathering move list: two headers"), 0);
2807                     ics_getting_history = H_FALSE;
2808                     break;
2809                 }
2810
2811                 /* Save player ratings into gameInfo if needed */
2812                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2813                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2814                     (gameInfo.whiteRating == -1 ||
2815                      gameInfo.blackRating == -1)) {
2816
2817                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2818                     gameInfo.blackRating = string_to_rating(star_match[3]);
2819                     if (appData.debugMode)
2820                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2821                               gameInfo.whiteRating, gameInfo.blackRating);
2822                 }
2823                 continue;
2824             }
2825
2826             if (looking_at(buf, &i,
2827               "* * match, initial time: * minute*, increment: * second")) {
2828                 /* Header for a move list -- second line */
2829                 /* Initial board will follow if this is a wild game */
2830                 if (gameInfo.event != NULL) free(gameInfo.event);
2831                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2832                 gameInfo.event = StrSave(str);
2833                 /* [HGM] we switched variant. Translate boards if needed. */
2834                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2835                 continue;
2836             }
2837
2838             if (looking_at(buf, &i, "Move  ")) {
2839                 /* Beginning of a move list */
2840                 switch (ics_getting_history) {
2841                   case H_FALSE:
2842                     /* Normally should not happen */
2843                     /* Maybe user hit reset while we were parsing */
2844                     break;
2845                   case H_REQUESTED:
2846                     /* Happens if we are ignoring a move list that is not
2847                      * the one we just requested.  Common if the user
2848                      * tries to observe two games without turning off
2849                      * getMoveList */
2850                     break;
2851                   case H_GETTING_MOVES:
2852                     /* Should not happen */
2853                     DisplayError(_("Error gathering move list: nested"), 0);
2854                     ics_getting_history = H_FALSE;
2855                     break;
2856                   case H_GOT_REQ_HEADER:
2857                     ics_getting_history = H_GETTING_MOVES;
2858                     started = STARTED_MOVES;
2859                     parse_pos = 0;
2860                     if (oldi > next_out) {
2861                         SendToPlayer(&buf[next_out], oldi - next_out);
2862                     }
2863                     break;
2864                   case H_GOT_UNREQ_HEADER:
2865                     ics_getting_history = H_GETTING_MOVES;
2866                     started = STARTED_MOVES_NOHIDE;
2867                     parse_pos = 0;
2868                     break;
2869                   case H_GOT_UNWANTED_HEADER:
2870                     ics_getting_history = H_FALSE;
2871                     break;
2872                 }
2873                 continue;
2874             }                           
2875             
2876             if (looking_at(buf, &i, "% ") ||
2877                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2878                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2879                 if(suppressKibitz) next_out = i;
2880                 savingComment = FALSE;
2881                 suppressKibitz = 0;
2882                 switch (started) {
2883                   case STARTED_MOVES:
2884                   case STARTED_MOVES_NOHIDE:
2885                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2886                     parse[parse_pos + i - oldi] = NULLCHAR;
2887                     ParseGameHistory(parse);
2888 #if ZIPPY
2889                     if (appData.zippyPlay && first.initDone) {
2890                         FeedMovesToProgram(&first, forwardMostMove);
2891                         if (gameMode == IcsPlayingWhite) {
2892                             if (WhiteOnMove(forwardMostMove)) {
2893                                 if (first.sendTime) {
2894                                   if (first.useColors) {
2895                                     SendToProgram("black\n", &first); 
2896                                   }
2897                                   SendTimeRemaining(&first, TRUE);
2898                                 }
2899                                 if (first.useColors) {
2900                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2901                                 }
2902                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2903                                 first.maybeThinking = TRUE;
2904                             } else {
2905                                 if (first.usePlayother) {
2906                                   if (first.sendTime) {
2907                                     SendTimeRemaining(&first, TRUE);
2908                                   }
2909                                   SendToProgram("playother\n", &first);
2910                                   firstMove = FALSE;
2911                                 } else {
2912                                   firstMove = TRUE;
2913                                 }
2914                             }
2915                         } else if (gameMode == IcsPlayingBlack) {
2916                             if (!WhiteOnMove(forwardMostMove)) {
2917                                 if (first.sendTime) {
2918                                   if (first.useColors) {
2919                                     SendToProgram("white\n", &first);
2920                                   }
2921                                   SendTimeRemaining(&first, FALSE);
2922                                 }
2923                                 if (first.useColors) {
2924                                   SendToProgram("black\n", &first);
2925                                 }
2926                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2927                                 first.maybeThinking = TRUE;
2928                             } else {
2929                                 if (first.usePlayother) {
2930                                   if (first.sendTime) {
2931                                     SendTimeRemaining(&first, FALSE);
2932                                   }
2933                                   SendToProgram("playother\n", &first);
2934                                   firstMove = FALSE;
2935                                 } else {
2936                                   firstMove = TRUE;
2937                                 }
2938                             }
2939                         }                       
2940                     }
2941 #endif
2942                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2943                         /* Moves came from oldmoves or moves command
2944                            while we weren't doing anything else.
2945                            */
2946                         currentMove = forwardMostMove;
2947                         ClearHighlights();/*!!could figure this out*/
2948                         flipView = appData.flipView;
2949                         DrawPosition(TRUE, boards[currentMove]);
2950                         DisplayBothClocks();
2951                         sprintf(str, "%s vs. %s",
2952                                 gameInfo.white, gameInfo.black);
2953                         DisplayTitle(str);
2954                         gameMode = IcsIdle;
2955                     } else {
2956                         /* Moves were history of an active game */
2957                         if (gameInfo.resultDetails != NULL) {
2958                             free(gameInfo.resultDetails);
2959                             gameInfo.resultDetails = NULL;
2960                         }
2961                     }
2962                     HistorySet(parseList, backwardMostMove,
2963                                forwardMostMove, currentMove-1);
2964                     DisplayMove(currentMove - 1);
2965                     if (started == STARTED_MOVES) next_out = i;
2966                     started = STARTED_NONE;
2967                     ics_getting_history = H_FALSE;
2968                     break;
2969
2970                   case STARTED_OBSERVE:
2971                     started = STARTED_NONE;
2972                     SendToICS(ics_prefix);
2973                     SendToICS("refresh\n");
2974                     break;
2975
2976                   default:
2977                     break;
2978                 }
2979                 if(bookHit) { // [HGM] book: simulate book reply
2980                     static char bookMove[MSG_SIZ]; // a bit generous?
2981
2982                     programStats.nodes = programStats.depth = programStats.time = 
2983                     programStats.score = programStats.got_only_move = 0;
2984                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2985
2986                     strcpy(bookMove, "move ");
2987                     strcat(bookMove, bookHit);
2988                     HandleMachineMove(bookMove, &first);
2989                 }
2990                 continue;
2991             }
2992             
2993             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2994                  started == STARTED_HOLDINGS ||
2995                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2996                 /* Accumulate characters in move list or board */
2997                 parse[parse_pos++] = buf[i];
2998             }
2999             
3000             /* Start of game messages.  Mostly we detect start of game
3001                when the first board image arrives.  On some versions
3002                of the ICS, though, we need to do a "refresh" after starting
3003                to observe in order to get the current board right away. */
3004             if (looking_at(buf, &i, "Adding game * to observation list")) {
3005                 started = STARTED_OBSERVE;
3006                 continue;
3007             }
3008
3009             /* Handle auto-observe */
3010             if (appData.autoObserve &&
3011                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3012                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3013                 char *player;
3014                 /* Choose the player that was highlighted, if any. */
3015                 if (star_match[0][0] == '\033' ||
3016                     star_match[1][0] != '\033') {
3017                     player = star_match[0];
3018                 } else {
3019                     player = star_match[2];
3020                 }
3021                 sprintf(str, "%sobserve %s\n",
3022                         ics_prefix, StripHighlightAndTitle(player));
3023                 SendToICS(str);
3024
3025                 /* Save ratings from notify string */
3026                 strcpy(player1Name, star_match[0]);
3027                 player1Rating = string_to_rating(star_match[1]);
3028                 strcpy(player2Name, star_match[2]);
3029                 player2Rating = string_to_rating(star_match[3]);
3030
3031                 if (appData.debugMode)
3032                   fprintf(debugFP, 
3033                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3034                           player1Name, player1Rating,
3035                           player2Name, player2Rating);
3036
3037                 continue;
3038             }
3039
3040             /* Deal with automatic examine mode after a game,
3041                and with IcsObserving -> IcsExamining transition */
3042             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3043                 looking_at(buf, &i, "has made you an examiner of game *")) {
3044
3045                 int gamenum = atoi(star_match[0]);
3046                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3047                     gamenum == ics_gamenum) {
3048                     /* We were already playing or observing this game;
3049                        no need to refetch history */
3050                     gameMode = IcsExamining;
3051                     if (pausing) {
3052                         pauseExamForwardMostMove = forwardMostMove;
3053                     } else if (currentMove < forwardMostMove) {
3054                         ForwardInner(forwardMostMove);
3055                     }
3056                 } else {
3057                     /* I don't think this case really can happen */
3058                     SendToICS(ics_prefix);
3059                     SendToICS("refresh\n");
3060                 }
3061                 continue;
3062             }    
3063             
3064             /* Error messages */
3065 //          if (ics_user_moved) {
3066             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3067                 if (looking_at(buf, &i, "Illegal move") ||
3068                     looking_at(buf, &i, "Not a legal move") ||
3069                     looking_at(buf, &i, "Your king is in check") ||
3070                     looking_at(buf, &i, "It isn't your turn") ||
3071                     looking_at(buf, &i, "It is not your move")) {
3072                     /* Illegal move */
3073                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3074                         currentMove = --forwardMostMove;
3075                         DisplayMove(currentMove - 1); /* before DMError */
3076                         DrawPosition(FALSE, boards[currentMove]);
3077                         SwitchClocks();
3078                         DisplayBothClocks();
3079                     }
3080                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3081                     ics_user_moved = 0;
3082                     continue;
3083                 }
3084             }
3085
3086             if (looking_at(buf, &i, "still have time") ||
3087                 looking_at(buf, &i, "not out of time") ||
3088                 looking_at(buf, &i, "either player is out of time") ||
3089                 looking_at(buf, &i, "has timeseal; checking")) {
3090                 /* We must have called his flag a little too soon */
3091                 whiteFlag = blackFlag = FALSE;
3092                 continue;
3093             }
3094
3095             if (looking_at(buf, &i, "added * seconds to") ||
3096                 looking_at(buf, &i, "seconds were added to")) {
3097                 /* Update the clocks */
3098                 SendToICS(ics_prefix);
3099                 SendToICS("refresh\n");
3100                 continue;
3101             }
3102
3103             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3104                 ics_clock_paused = TRUE;
3105                 StopClocks();
3106                 continue;
3107             }
3108
3109             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3110                 ics_clock_paused = FALSE;
3111                 StartClocks();
3112                 continue;
3113             }
3114
3115             /* Grab player ratings from the Creating: message.
3116                Note we have to check for the special case when
3117                the ICS inserts things like [white] or [black]. */
3118             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3119                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3120                 /* star_matches:
3121                    0    player 1 name (not necessarily white)
3122                    1    player 1 rating
3123                    2    empty, white, or black (IGNORED)
3124                    3    player 2 name (not necessarily black)
3125                    4    player 2 rating
3126                    
3127                    The names/ratings are sorted out when the game
3128                    actually starts (below).
3129                 */
3130                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3131                 player1Rating = string_to_rating(star_match[1]);
3132                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3133                 player2Rating = string_to_rating(star_match[4]);
3134
3135                 if (appData.debugMode)
3136                   fprintf(debugFP, 
3137                           "Ratings from 'Creating:' %s %d, %s %d\n",
3138                           player1Name, player1Rating,
3139                           player2Name, player2Rating);
3140
3141                 continue;
3142             }
3143             
3144             /* Improved generic start/end-of-game messages */
3145             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3146                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3147                 /* If tkind == 0: */
3148                 /* star_match[0] is the game number */
3149                 /*           [1] is the white player's name */
3150                 /*           [2] is the black player's name */
3151                 /* For end-of-game: */
3152                 /*           [3] is the reason for the game end */
3153                 /*           [4] is a PGN end game-token, preceded by " " */
3154                 /* For start-of-game: */
3155                 /*           [3] begins with "Creating" or "Continuing" */
3156                 /*           [4] is " *" or empty (don't care). */
3157                 int gamenum = atoi(star_match[0]);
3158                 char *whitename, *blackname, *why, *endtoken;
3159                 ChessMove endtype = (ChessMove) 0;
3160
3161                 if (tkind == 0) {
3162                   whitename = star_match[1];
3163                   blackname = star_match[2];
3164                   why = star_match[3];
3165                   endtoken = star_match[4];
3166                 } else {
3167                   whitename = star_match[1];
3168                   blackname = star_match[3];
3169                   why = star_match[5];
3170                   endtoken = star_match[6];
3171                 }
3172
3173                 /* Game start messages */
3174                 if (strncmp(why, "Creating ", 9) == 0 ||
3175                     strncmp(why, "Continuing ", 11) == 0) {
3176                     gs_gamenum = gamenum;
3177                     strcpy(gs_kind, strchr(why, ' ') + 1);
3178                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3179 #if ZIPPY
3180                     if (appData.zippyPlay) {
3181                         ZippyGameStart(whitename, blackname);
3182                     }
3183 #endif /*ZIPPY*/
3184                     continue;
3185                 }
3186
3187                 /* Game end messages */
3188                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3189                     ics_gamenum != gamenum) {
3190                     continue;
3191                 }
3192                 while (endtoken[0] == ' ') endtoken++;
3193                 switch (endtoken[0]) {
3194                   case '*':
3195                   default:
3196                     endtype = GameUnfinished;
3197                     break;
3198                   case '0':
3199                     endtype = BlackWins;
3200                     break;
3201                   case '1':
3202                     if (endtoken[1] == '/')
3203                       endtype = GameIsDrawn;
3204                     else
3205                       endtype = WhiteWins;
3206                     break;
3207                 }
3208                 GameEnds(endtype, why, GE_ICS);
3209 #if ZIPPY
3210                 if (appData.zippyPlay && first.initDone) {
3211                     ZippyGameEnd(endtype, why);
3212                     if (first.pr == NULL) {
3213                       /* Start the next process early so that we'll
3214                          be ready for the next challenge */
3215                       StartChessProgram(&first);
3216                     }
3217                     /* Send "new" early, in case this command takes
3218                        a long time to finish, so that we'll be ready
3219                        for the next challenge. */
3220                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3221                     Reset(TRUE, TRUE);
3222                 }
3223 #endif /*ZIPPY*/
3224                 continue;
3225             }
3226
3227             if (looking_at(buf, &i, "Removing game * from observation") ||
3228                 looking_at(buf, &i, "no longer observing game *") ||
3229                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3230                 if (gameMode == IcsObserving &&
3231                     atoi(star_match[0]) == ics_gamenum)
3232                   {
3233                       /* icsEngineAnalyze */
3234                       if (appData.icsEngineAnalyze) {
3235                             ExitAnalyzeMode();
3236                             ModeHighlight();
3237                       }
3238                       StopClocks();
3239                       gameMode = IcsIdle;
3240                       ics_gamenum = -1;
3241                       ics_user_moved = FALSE;
3242                   }
3243                 continue;
3244             }
3245
3246             if (looking_at(buf, &i, "no longer examining game *")) {
3247                 if (gameMode == IcsExamining &&
3248                     atoi(star_match[0]) == ics_gamenum)
3249                   {
3250                       gameMode = IcsIdle;
3251                       ics_gamenum = -1;
3252                       ics_user_moved = FALSE;
3253                   }
3254                 continue;
3255             }
3256
3257             /* Advance leftover_start past any newlines we find,
3258                so only partial lines can get reparsed */
3259             if (looking_at(buf, &i, "\n")) {
3260                 prevColor = curColor;
3261                 if (curColor != ColorNormal) {
3262                     if (oldi > next_out) {
3263                         SendToPlayer(&buf[next_out], oldi - next_out);
3264                         next_out = oldi;
3265                     }
3266                     Colorize(ColorNormal, FALSE);
3267                     curColor = ColorNormal;
3268                 }
3269                 if (started == STARTED_BOARD) {
3270                     started = STARTED_NONE;
3271                     parse[parse_pos] = NULLCHAR;
3272                     ParseBoard12(parse);
3273                     ics_user_moved = 0;
3274
3275                     /* Send premove here */
3276                     if (appData.premove) {
3277                       char str[MSG_SIZ];
3278                       if (currentMove == 0 &&
3279                           gameMode == IcsPlayingWhite &&
3280                           appData.premoveWhite) {
3281                         sprintf(str, "%s\n", appData.premoveWhiteText);
3282                         if (appData.debugMode)
3283                           fprintf(debugFP, "Sending premove:\n");
3284                         SendToICS(str);
3285                       } else if (currentMove == 1 &&
3286                                  gameMode == IcsPlayingBlack &&
3287                                  appData.premoveBlack) {
3288                         sprintf(str, "%s\n", appData.premoveBlackText);
3289                         if (appData.debugMode)
3290                           fprintf(debugFP, "Sending premove:\n");
3291                         SendToICS(str);
3292                       } else if (gotPremove) {
3293                         gotPremove = 0;
3294                         ClearPremoveHighlights();
3295                         if (appData.debugMode)
3296                           fprintf(debugFP, "Sending premove:\n");
3297                           UserMoveEvent(premoveFromX, premoveFromY, 
3298                                         premoveToX, premoveToY, 
3299                                         premovePromoChar);
3300                       }
3301                     }
3302
3303                     /* Usually suppress following prompt */
3304                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3305                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3306                         if (looking_at(buf, &i, "*% ")) {
3307                             savingComment = FALSE;
3308                             suppressKibitz = 0;
3309                         }
3310                     }
3311                     next_out = i;
3312                 } else if (started == STARTED_HOLDINGS) {
3313                     int gamenum;
3314                     char new_piece[MSG_SIZ];
3315                     started = STARTED_NONE;
3316                     parse[parse_pos] = NULLCHAR;
3317                     if (appData.debugMode)
3318                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3319                                                         parse, currentMove);
3320                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3321                         gamenum == ics_gamenum) {
3322                         if (gameInfo.variant == VariantNormal) {
3323                           /* [HGM] We seem to switch variant during a game!
3324                            * Presumably no holdings were displayed, so we have
3325                            * to move the position two files to the right to
3326                            * create room for them!
3327                            */
3328                           VariantClass newVariant;
3329                           switch(gameInfo.boardWidth) { // base guess on board width
3330                                 case 9:  newVariant = VariantShogi; break;
3331                                 case 10: newVariant = VariantGreat; break;
3332                                 default: newVariant = VariantCrazyhouse; break;
3333                           }
3334                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3335                           /* Get a move list just to see the header, which
3336                              will tell us whether this is really bug or zh */
3337                           if (ics_getting_history == H_FALSE) {
3338                             ics_getting_history = H_REQUESTED;
3339                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3340                             SendToICS(str);
3341                           }
3342                         }
3343                         new_piece[0] = NULLCHAR;
3344                         sscanf(parse, "game %d white [%s black [%s <- %s",
3345                                &gamenum, white_holding, black_holding,
3346                                new_piece);
3347                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3348                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3349                         /* [HGM] copy holdings to board holdings area */
3350                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3351                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3352                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3353 #if ZIPPY
3354                         if (appData.zippyPlay && first.initDone) {
3355                             ZippyHoldings(white_holding, black_holding,
3356                                           new_piece);
3357                         }
3358 #endif /*ZIPPY*/
3359                         if (tinyLayout || smallLayout) {
3360                             char wh[16], bh[16];
3361                             PackHolding(wh, white_holding);
3362                             PackHolding(bh, black_holding);
3363                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3364                                     gameInfo.white, gameInfo.black);
3365                         } else {
3366                             sprintf(str, "%s [%s] vs. %s [%s]",
3367                                     gameInfo.white, white_holding,
3368                                     gameInfo.black, black_holding);
3369                         }
3370
3371                         DrawPosition(FALSE, boards[currentMove]);
3372                         DisplayTitle(str);
3373                     }
3374                     /* Suppress following prompt */
3375                     if (looking_at(buf, &i, "*% ")) {
3376                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3377                         savingComment = FALSE;
3378                         suppressKibitz = 0;
3379                     }
3380                     next_out = i;
3381                 }
3382                 continue;
3383             }
3384
3385             i++;                /* skip unparsed character and loop back */
3386         }
3387         
3388         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3389 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3390 //          SendToPlayer(&buf[next_out], i - next_out);
3391             started != STARTED_HOLDINGS && leftover_start > next_out) {
3392             SendToPlayer(&buf[next_out], leftover_start - next_out);
3393             next_out = i;
3394         }
3395         
3396         leftover_len = buf_len - leftover_start;
3397         /* if buffer ends with something we couldn't parse,
3398            reparse it after appending the next read */
3399         
3400     } else if (count == 0) {
3401         RemoveInputSource(isr);
3402         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3403     } else {
3404         DisplayFatalError(_("Error reading from ICS"), error, 1);
3405     }
3406 }
3407
3408
3409 /* Board style 12 looks like this:
3410    
3411    <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
3412    
3413  * The "<12> " is stripped before it gets to this routine.  The two
3414  * trailing 0's (flip state and clock ticking) are later addition, and
3415  * some chess servers may not have them, or may have only the first.
3416  * Additional trailing fields may be added in the future.  
3417  */
3418
3419 #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"
3420
3421 #define RELATION_OBSERVING_PLAYED    0
3422 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3423 #define RELATION_PLAYING_MYMOVE      1
3424 #define RELATION_PLAYING_NOTMYMOVE  -1
3425 #define RELATION_EXAMINING           2
3426 #define RELATION_ISOLATED_BOARD     -3
3427 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3428
3429 void
3430 ParseBoard12(string)
3431      char *string;
3432
3433     GameMode newGameMode;
3434     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3435     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3436     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3437     char to_play, board_chars[200];
3438     char move_str[500], str[500], elapsed_time[500];
3439     char black[32], white[32];
3440     Board board;
3441     int prevMove = currentMove;
3442     int ticking = 2;
3443     ChessMove moveType;
3444     int fromX, fromY, toX, toY;
3445     char promoChar;
3446     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3447     char *bookHit = NULL; // [HGM] book
3448     Boolean weird = FALSE, reqFlag = FALSE;
3449
3450     fromX = fromY = toX = toY = -1;
3451     
3452     newGame = FALSE;
3453
3454     if (appData.debugMode)
3455       fprintf(debugFP, _("Parsing board: %s\n"), string);
3456
3457     move_str[0] = NULLCHAR;
3458     elapsed_time[0] = NULLCHAR;
3459     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3460         int  i = 0, j;
3461         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3462             if(string[i] == ' ') { ranks++; files = 0; }
3463             else files++;
3464             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3465             i++;
3466         }
3467         for(j = 0; j <i; j++) board_chars[j] = string[j];
3468         board_chars[i] = '\0';
3469         string += i + 1;
3470     }
3471     n = sscanf(string, PATTERN, &to_play, &double_push,
3472                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3473                &gamenum, white, black, &relation, &basetime, &increment,
3474                &white_stren, &black_stren, &white_time, &black_time,
3475                &moveNum, str, elapsed_time, move_str, &ics_flip,
3476                &ticking);
3477
3478     if (n < 21) {
3479         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3480         DisplayError(str, 0);
3481         return;
3482     }
3483
3484     /* Convert the move number to internal form */
3485     moveNum = (moveNum - 1) * 2;
3486     if (to_play == 'B') moveNum++;
3487     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3488       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3489                         0, 1);
3490       return;
3491     }
3492     
3493     switch (relation) {
3494       case RELATION_OBSERVING_PLAYED:
3495       case RELATION_OBSERVING_STATIC:
3496         if (gamenum == -1) {
3497             /* Old ICC buglet */
3498             relation = RELATION_OBSERVING_STATIC;
3499         }
3500         newGameMode = IcsObserving;
3501         break;
3502       case RELATION_PLAYING_MYMOVE:
3503       case RELATION_PLAYING_NOTMYMOVE:
3504         newGameMode =
3505           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3506             IcsPlayingWhite : IcsPlayingBlack;
3507         break;
3508       case RELATION_EXAMINING:
3509         newGameMode = IcsExamining;
3510         break;
3511       case RELATION_ISOLATED_BOARD:
3512       default:
3513         /* Just display this board.  If user was doing something else,
3514            we will forget about it until the next board comes. */ 
3515         newGameMode = IcsIdle;
3516         break;
3517       case RELATION_STARTING_POSITION:
3518         newGameMode = gameMode;
3519         break;
3520     }
3521     
3522     /* Modify behavior for initial board display on move listing
3523        of wild games.
3524        */
3525     switch (ics_getting_history) {
3526       case H_FALSE:
3527       case H_REQUESTED:
3528         break;
3529       case H_GOT_REQ_HEADER:
3530       case H_GOT_UNREQ_HEADER:
3531         /* This is the initial position of the current game */
3532         gamenum = ics_gamenum;
3533         moveNum = 0;            /* old ICS bug workaround */
3534         if (to_play == 'B') {
3535           startedFromSetupPosition = TRUE;
3536           blackPlaysFirst = TRUE;
3537           moveNum = 1;
3538           if (forwardMostMove == 0) forwardMostMove = 1;
3539           if (backwardMostMove == 0) backwardMostMove = 1;
3540           if (currentMove == 0) currentMove = 1;
3541         }
3542         newGameMode = gameMode;
3543         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3544         break;
3545       case H_GOT_UNWANTED_HEADER:
3546         /* This is an initial board that we don't want */
3547         return;
3548       case H_GETTING_MOVES:
3549         /* Should not happen */
3550         DisplayError(_("Error gathering move list: extra board"), 0);
3551         ics_getting_history = H_FALSE;
3552         return;
3553     }
3554
3555    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3556                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3557      /* [HGM] We seem to have switched variant unexpectedly
3558       * Try to guess new variant from board size
3559       */
3560           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3561           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3562           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3563           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3564           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3565           if(!weird) newVariant = VariantNormal;
3566           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3567           /* Get a move list just to see the header, which
3568              will tell us whether this is really bug or zh */
3569           if (ics_getting_history == H_FALSE) {
3570             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3571             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3572             SendToICS(str);
3573           }
3574     }
3575     
3576     /* Take action if this is the first board of a new game, or of a
3577        different game than is currently being displayed.  */
3578     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3579         relation == RELATION_ISOLATED_BOARD) {
3580         
3581         /* Forget the old game and get the history (if any) of the new one */
3582         if (gameMode != BeginningOfGame) {
3583           Reset(TRUE, TRUE);
3584         }
3585         newGame = TRUE;
3586         if (appData.autoRaiseBoard) BoardToTop();
3587         prevMove = -3;
3588         if (gamenum == -1) {
3589             newGameMode = IcsIdle;
3590         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3591                    appData.getMoveList && !reqFlag) {
3592             /* Need to get game history */
3593             ics_getting_history = H_REQUESTED;
3594             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3595             SendToICS(str);
3596         }
3597         
3598         /* Initially flip the board to have black on the bottom if playing
3599            black or if the ICS flip flag is set, but let the user change
3600            it with the Flip View button. */
3601         flipView = appData.autoFlipView ? 
3602           (newGameMode == IcsPlayingBlack) || ics_flip :
3603           appData.flipView;
3604         
3605         /* Done with values from previous mode; copy in new ones */
3606         gameMode = newGameMode;
3607         ModeHighlight();
3608         ics_gamenum = gamenum;
3609         if (gamenum == gs_gamenum) {
3610             int klen = strlen(gs_kind);
3611             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3612             sprintf(str, "ICS %s", gs_kind);
3613             gameInfo.event = StrSave(str);
3614         } else {
3615             gameInfo.event = StrSave("ICS game");
3616         }
3617         gameInfo.site = StrSave(appData.icsHost);
3618         gameInfo.date = PGNDate();
3619         gameInfo.round = StrSave("-");
3620         gameInfo.white = StrSave(white);
3621         gameInfo.black = StrSave(black);
3622         timeControl = basetime * 60 * 1000;
3623         timeControl_2 = 0;
3624         timeIncrement = increment * 1000;
3625         movesPerSession = 0;
3626         gameInfo.timeControl = TimeControlTagValue();
3627         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3628   if (appData.debugMode) {
3629     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3630     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3631     setbuf(debugFP, NULL);
3632   }
3633
3634         gameInfo.outOfBook = NULL;
3635         
3636         /* Do we have the ratings? */
3637         if (strcmp(player1Name, white) == 0 &&
3638             strcmp(player2Name, black) == 0) {
3639             if (appData.debugMode)
3640               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3641                       player1Rating, player2Rating);
3642             gameInfo.whiteRating = player1Rating;
3643             gameInfo.blackRating = player2Rating;
3644         } else if (strcmp(player2Name, white) == 0 &&
3645                    strcmp(player1Name, black) == 0) {
3646             if (appData.debugMode)
3647               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3648                       player2Rating, player1Rating);
3649             gameInfo.whiteRating = player2Rating;
3650             gameInfo.blackRating = player1Rating;
3651         }
3652         player1Name[0] = player2Name[0] = NULLCHAR;
3653
3654         /* Silence shouts if requested */
3655         if (appData.quietPlay &&
3656             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3657             SendToICS(ics_prefix);
3658             SendToICS("set shout 0\n");
3659         }
3660     }
3661     
3662     /* Deal with midgame name changes */
3663     if (!newGame) {
3664         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3665             if (gameInfo.white) free(gameInfo.white);
3666             gameInfo.white = StrSave(white);
3667         }
3668         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3669             if (gameInfo.black) free(gameInfo.black);
3670             gameInfo.black = StrSave(black);
3671         }
3672     }
3673     
3674     /* Throw away game result if anything actually changes in examine mode */
3675     if (gameMode == IcsExamining && !newGame) {
3676         gameInfo.result = GameUnfinished;
3677         if (gameInfo.resultDetails != NULL) {
3678             free(gameInfo.resultDetails);
3679             gameInfo.resultDetails = NULL;
3680         }
3681     }
3682     
3683     /* In pausing && IcsExamining mode, we ignore boards coming
3684        in if they are in a different variation than we are. */
3685     if (pauseExamInvalid) return;
3686     if (pausing && gameMode == IcsExamining) {
3687         if (moveNum <= pauseExamForwardMostMove) {
3688             pauseExamInvalid = TRUE;
3689             forwardMostMove = pauseExamForwardMostMove;
3690             return;
3691         }
3692     }
3693     
3694   if (appData.debugMode) {
3695     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3696   }
3697     /* Parse the board */
3698     for (k = 0; k < ranks; k++) {
3699       for (j = 0; j < files; j++)
3700         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3701       if(gameInfo.holdingsWidth > 1) {
3702            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3703            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3704       }
3705     }
3706     CopyBoard(boards[moveNum], board);
3707     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3708     if (moveNum == 0) {
3709         startedFromSetupPosition =
3710           !CompareBoards(board, initialPosition);
3711         if(startedFromSetupPosition)
3712             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3713     }
3714
3715     /* [HGM] Set castling rights. Take the outermost Rooks,
3716        to make it also work for FRC opening positions. Note that board12
3717        is really defective for later FRC positions, as it has no way to
3718        indicate which Rook can castle if they are on the same side of King.
3719        For the initial position we grant rights to the outermost Rooks,
3720        and remember thos rights, and we then copy them on positions
3721        later in an FRC game. This means WB might not recognize castlings with
3722        Rooks that have moved back to their original position as illegal,
3723        but in ICS mode that is not its job anyway.
3724     */
3725     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3726     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3727
3728         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3729             if(board[0][i] == WhiteRook) j = i;
3730         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3731         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3732             if(board[0][i] == WhiteRook) j = i;
3733         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3734         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3735             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3736         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3737         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3738             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3739         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3740
3741         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3742         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3743             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3744         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3745             if(board[BOARD_HEIGHT-1][k] == bKing)
3746                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3747         if(gameInfo.variant == VariantTwoKings) {
3748             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3749             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3750             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3751         }
3752     } else { int r;
3753         r = boards[moveNum][CASTLING][0] = initialRights[0];
3754         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3755         r = boards[moveNum][CASTLING][1] = initialRights[1];
3756         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3757         r = boards[moveNum][CASTLING][3] = initialRights[3];
3758         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3759         r = boards[moveNum][CASTLING][4] = initialRights[4];
3760         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3761         /* wildcastle kludge: always assume King has rights */
3762         r = boards[moveNum][CASTLING][2] = initialRights[2];
3763         r = boards[moveNum][CASTLING][5] = initialRights[5];
3764     }
3765     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3766     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3767
3768     
3769     if (ics_getting_history == H_GOT_REQ_HEADER ||
3770         ics_getting_history == H_GOT_UNREQ_HEADER) {
3771         /* This was an initial position from a move list, not
3772            the current position */
3773         return;
3774     }
3775     
3776     /* Update currentMove and known move number limits */
3777     newMove = newGame || moveNum > forwardMostMove;
3778
3779     if (newGame) {
3780         forwardMostMove = backwardMostMove = currentMove = moveNum;
3781         if (gameMode == IcsExamining && moveNum == 0) {
3782           /* Workaround for ICS limitation: we are not told the wild
3783              type when starting to examine a game.  But if we ask for
3784              the move list, the move list header will tell us */
3785             ics_getting_history = H_REQUESTED;
3786             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3787             SendToICS(str);
3788         }
3789     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3790                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3791 #if ZIPPY
3792         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3793         /* [HGM] applied this also to an engine that is silently watching        */
3794         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3795             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3796             gameInfo.variant == currentlyInitializedVariant) {
3797           takeback = forwardMostMove - moveNum;
3798           for (i = 0; i < takeback; i++) {
3799             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3800             SendToProgram("undo\n", &first);
3801           }
3802         }
3803 #endif
3804
3805         forwardMostMove = moveNum;
3806         if (!pausing || currentMove > forwardMostMove)
3807           currentMove = forwardMostMove;
3808     } else {
3809         /* New part of history that is not contiguous with old part */ 
3810         if (pausing && gameMode == IcsExamining) {
3811             pauseExamInvalid = TRUE;
3812             forwardMostMove = pauseExamForwardMostMove;
3813             return;
3814         }
3815         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3816 #if ZIPPY
3817             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3818                 // [HGM] when we will receive the move list we now request, it will be
3819                 // fed to the engine from the first move on. So if the engine is not
3820                 // in the initial position now, bring it there.
3821                 InitChessProgram(&first, 0);
3822             }
3823 #endif
3824             ics_getting_history = H_REQUESTED;
3825             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3826             SendToICS(str);
3827         }
3828         forwardMostMove = backwardMostMove = currentMove = moveNum;
3829     }
3830     
3831     /* Update the clocks */
3832     if (strchr(elapsed_time, '.')) {
3833       /* Time is in ms */
3834       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3835       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3836     } else {
3837       /* Time is in seconds */
3838       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3839       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3840     }
3841       
3842
3843 #if ZIPPY
3844     if (appData.zippyPlay && newGame &&
3845         gameMode != IcsObserving && gameMode != IcsIdle &&
3846         gameMode != IcsExamining)
3847       ZippyFirstBoard(moveNum, basetime, increment);
3848 #endif
3849     
3850     /* Put the move on the move list, first converting
3851        to canonical algebraic form. */
3852     if (moveNum > 0) {
3853   if (appData.debugMode) {
3854     if (appData.debugMode) { int f = forwardMostMove;
3855         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3856                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3857                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3858     }
3859     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3860     fprintf(debugFP, "moveNum = %d\n", moveNum);
3861     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3862     setbuf(debugFP, NULL);
3863   }
3864         if (moveNum <= backwardMostMove) {
3865             /* We don't know what the board looked like before
3866                this move.  Punt. */
3867             strcpy(parseList[moveNum - 1], move_str);
3868             strcat(parseList[moveNum - 1], " ");
3869             strcat(parseList[moveNum - 1], elapsed_time);
3870             moveList[moveNum - 1][0] = NULLCHAR;
3871         } else if (strcmp(move_str, "none") == 0) {
3872             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3873             /* Again, we don't know what the board looked like;
3874                this is really the start of the game. */
3875             parseList[moveNum - 1][0] = NULLCHAR;
3876             moveList[moveNum - 1][0] = NULLCHAR;
3877             backwardMostMove = moveNum;
3878             startedFromSetupPosition = TRUE;
3879             fromX = fromY = toX = toY = -1;
3880         } else {
3881           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3882           //                 So we parse the long-algebraic move string in stead of the SAN move
3883           int valid; char buf[MSG_SIZ], *prom;
3884
3885           // str looks something like "Q/a1-a2"; kill the slash
3886           if(str[1] == '/') 
3887                 sprintf(buf, "%c%s", str[0], str+2);
3888           else  strcpy(buf, str); // might be castling
3889           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3890                 strcat(buf, prom); // long move lacks promo specification!
3891           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3892                 if(appData.debugMode) 
3893                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3894                 strcpy(move_str, buf);
3895           }
3896           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3897                                 &fromX, &fromY, &toX, &toY, &promoChar)
3898                || ParseOneMove(buf, moveNum - 1, &moveType,
3899                                 &fromX, &fromY, &toX, &toY, &promoChar);
3900           // end of long SAN patch
3901           if (valid) {
3902             (void) CoordsToAlgebraic(boards[moveNum - 1],
3903                                      PosFlags(moveNum - 1),
3904                                      fromY, fromX, toY, toX, promoChar,
3905                                      parseList[moveNum-1]);
3906             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3907               case MT_NONE:
3908               case MT_STALEMATE:
3909               default:
3910                 break;
3911               case MT_CHECK:
3912                 if(gameInfo.variant != VariantShogi)
3913                     strcat(parseList[moveNum - 1], "+");
3914                 break;
3915               case MT_CHECKMATE:
3916               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3917                 strcat(parseList[moveNum - 1], "#");
3918                 break;
3919             }
3920             strcat(parseList[moveNum - 1], " ");
3921             strcat(parseList[moveNum - 1], elapsed_time);
3922             /* currentMoveString is set as a side-effect of ParseOneMove */
3923             strcpy(moveList[moveNum - 1], currentMoveString);
3924             strcat(moveList[moveNum - 1], "\n");
3925           } else {
3926             /* Move from ICS was illegal!?  Punt. */
3927   if (appData.debugMode) {
3928     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3929     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3930   }
3931             strcpy(parseList[moveNum - 1], move_str);
3932             strcat(parseList[moveNum - 1], " ");
3933             strcat(parseList[moveNum - 1], elapsed_time);
3934             moveList[moveNum - 1][0] = NULLCHAR;
3935             fromX = fromY = toX = toY = -1;
3936           }
3937         }
3938   if (appData.debugMode) {
3939     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3940     setbuf(debugFP, NULL);
3941   }
3942
3943 #if ZIPPY
3944         /* Send move to chess program (BEFORE animating it). */
3945         if (appData.zippyPlay && !newGame && newMove && 
3946            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3947
3948             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3949                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3950                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3951                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3952                             move_str);
3953                     DisplayError(str, 0);
3954                 } else {
3955                     if (first.sendTime) {
3956                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3957                     }
3958                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3959                     if (firstMove && !bookHit) {
3960                         firstMove = FALSE;
3961                         if (first.useColors) {
3962                           SendToProgram(gameMode == IcsPlayingWhite ?
3963                                         "white\ngo\n" :
3964                                         "black\ngo\n", &first);
3965                         } else {
3966                           SendToProgram("go\n", &first);
3967                         }
3968                         first.maybeThinking = TRUE;
3969                     }
3970                 }
3971             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3972               if (moveList[moveNum - 1][0] == NULLCHAR) {
3973                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3974                 DisplayError(str, 0);
3975               } else {
3976                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3977                 SendMoveToProgram(moveNum - 1, &first);
3978               }
3979             }
3980         }
3981 #endif
3982     }
3983
3984     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3985         /* If move comes from a remote source, animate it.  If it
3986            isn't remote, it will have already been animated. */
3987         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3988             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3989         }
3990         if (!pausing && appData.highlightLastMove) {
3991             SetHighlights(fromX, fromY, toX, toY);
3992         }
3993     }
3994     
3995     /* Start the clocks */
3996     whiteFlag = blackFlag = FALSE;
3997     appData.clockMode = !(basetime == 0 && increment == 0);
3998     if (ticking == 0) {
3999       ics_clock_paused = TRUE;
4000       StopClocks();
4001     } else if (ticking == 1) {
4002       ics_clock_paused = FALSE;
4003     }
4004     if (gameMode == IcsIdle ||
4005         relation == RELATION_OBSERVING_STATIC ||
4006         relation == RELATION_EXAMINING ||
4007         ics_clock_paused)
4008       DisplayBothClocks();
4009     else
4010       StartClocks();
4011     
4012     /* Display opponents and material strengths */
4013     if (gameInfo.variant != VariantBughouse &&
4014         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4015         if (tinyLayout || smallLayout) {
4016             if(gameInfo.variant == VariantNormal)
4017                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4018                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4019                     basetime, increment);
4020             else
4021                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4022                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4023                     basetime, increment, (int) gameInfo.variant);
4024         } else {
4025             if(gameInfo.variant == VariantNormal)
4026                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4027                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4028                     basetime, increment);
4029             else
4030                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4031                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4032                     basetime, increment, VariantName(gameInfo.variant));
4033         }
4034         DisplayTitle(str);
4035   if (appData.debugMode) {
4036     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4037   }
4038     }
4039
4040    
4041     /* Display the board */
4042     if (!pausing && !appData.noGUI) {
4043       
4044       if (appData.premove)
4045           if (!gotPremove || 
4046              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4047              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4048               ClearPremoveHighlights();
4049
4050       DrawPosition(FALSE, boards[currentMove]);
4051       DisplayMove(moveNum - 1);
4052       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4053             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4054               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4055         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4056       }
4057     }
4058
4059     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4060 #if ZIPPY
4061     if(bookHit) { // [HGM] book: simulate book reply
4062         static char bookMove[MSG_SIZ]; // a bit generous?
4063
4064         programStats.nodes = programStats.depth = programStats.time = 
4065         programStats.score = programStats.got_only_move = 0;
4066         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4067
4068         strcpy(bookMove, "move ");
4069         strcat(bookMove, bookHit);
4070         HandleMachineMove(bookMove, &first);
4071     }
4072 #endif
4073 }
4074
4075 void
4076 GetMoveListEvent()
4077 {
4078     char buf[MSG_SIZ];
4079     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4080         ics_getting_history = H_REQUESTED;
4081         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4082         SendToICS(buf);
4083     }
4084 }
4085
4086 void
4087 AnalysisPeriodicEvent(force)
4088      int force;
4089 {
4090     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4091          && !force) || !appData.periodicUpdates)
4092       return;
4093
4094     /* Send . command to Crafty to collect stats */
4095     SendToProgram(".\n", &first);
4096
4097     /* Don't send another until we get a response (this makes
4098        us stop sending to old Crafty's which don't understand
4099        the "." command (sending illegal cmds resets node count & time,
4100        which looks bad)) */
4101     programStats.ok_to_send = 0;
4102 }
4103
4104 void ics_update_width(new_width)
4105         int new_width;
4106 {
4107         ics_printf("set width %d\n", new_width);
4108 }
4109
4110 void
4111 SendMoveToProgram(moveNum, cps)
4112      int moveNum;
4113      ChessProgramState *cps;
4114 {
4115     char buf[MSG_SIZ];
4116
4117     if (cps->useUsermove) {
4118       SendToProgram("usermove ", cps);
4119     }
4120     if (cps->useSAN) {
4121       char *space;
4122       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4123         int len = space - parseList[moveNum];
4124         memcpy(buf, parseList[moveNum], len);
4125         buf[len++] = '\n';
4126         buf[len] = NULLCHAR;
4127       } else {
4128         sprintf(buf, "%s\n", parseList[moveNum]);
4129       }
4130       SendToProgram(buf, cps);
4131     } else {
4132       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4133         AlphaRank(moveList[moveNum], 4);
4134         SendToProgram(moveList[moveNum], cps);
4135         AlphaRank(moveList[moveNum], 4); // and back
4136       } else
4137       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4138        * the engine. It would be nice to have a better way to identify castle 
4139        * moves here. */
4140       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4141                                                                          && cps->useOOCastle) {
4142         int fromX = moveList[moveNum][0] - AAA; 
4143         int fromY = moveList[moveNum][1] - ONE;
4144         int toX = moveList[moveNum][2] - AAA; 
4145         int toY = moveList[moveNum][3] - ONE;
4146         if((boards[moveNum][fromY][fromX] == WhiteKing 
4147             && boards[moveNum][toY][toX] == WhiteRook)
4148            || (boards[moveNum][fromY][fromX] == BlackKing 
4149                && boards[moveNum][toY][toX] == BlackRook)) {
4150           if(toX > fromX) SendToProgram("O-O\n", cps);
4151           else SendToProgram("O-O-O\n", cps);
4152         }
4153         else SendToProgram(moveList[moveNum], cps);
4154       }
4155       else SendToProgram(moveList[moveNum], cps);
4156       /* End of additions by Tord */
4157     }
4158
4159     /* [HGM] setting up the opening has brought engine in force mode! */
4160     /*       Send 'go' if we are in a mode where machine should play. */
4161     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4162         (gameMode == TwoMachinesPlay   ||
4163 #ifdef ZIPPY
4164          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4165 #endif
4166          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4167         SendToProgram("go\n", cps);
4168   if (appData.debugMode) {
4169     fprintf(debugFP, "(extra)\n");
4170   }
4171     }
4172     setboardSpoiledMachineBlack = 0;
4173 }
4174
4175 void
4176 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4177      ChessMove moveType;
4178      int fromX, fromY, toX, toY;
4179 {
4180     char user_move[MSG_SIZ];
4181
4182     switch (moveType) {
4183       default:
4184         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4185                 (int)moveType, fromX, fromY, toX, toY);
4186         DisplayError(user_move + strlen("say "), 0);
4187         break;
4188       case WhiteKingSideCastle:
4189       case BlackKingSideCastle:
4190       case WhiteQueenSideCastleWild:
4191       case BlackQueenSideCastleWild:
4192       /* PUSH Fabien */
4193       case WhiteHSideCastleFR:
4194       case BlackHSideCastleFR:
4195       /* POP Fabien */
4196         sprintf(user_move, "o-o\n");
4197         break;
4198       case WhiteQueenSideCastle:
4199       case BlackQueenSideCastle:
4200       case WhiteKingSideCastleWild:
4201       case BlackKingSideCastleWild:
4202       /* PUSH Fabien */
4203       case WhiteASideCastleFR:
4204       case BlackASideCastleFR:
4205       /* POP Fabien */
4206         sprintf(user_move, "o-o-o\n");
4207         break;
4208       case WhitePromotionQueen:
4209       case BlackPromotionQueen:
4210       case WhitePromotionRook:
4211       case BlackPromotionRook:
4212       case WhitePromotionBishop:
4213       case BlackPromotionBishop:
4214       case WhitePromotionKnight:
4215       case BlackPromotionKnight:
4216       case WhitePromotionKing:
4217       case BlackPromotionKing:
4218       case WhitePromotionChancellor:
4219       case BlackPromotionChancellor:
4220       case WhitePromotionArchbishop:
4221       case BlackPromotionArchbishop:
4222         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4223             sprintf(user_move, "%c%c%c%c=%c\n",
4224                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4225                 PieceToChar(WhiteFerz));
4226         else if(gameInfo.variant == VariantGreat)
4227             sprintf(user_move, "%c%c%c%c=%c\n",
4228                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4229                 PieceToChar(WhiteMan));
4230         else
4231             sprintf(user_move, "%c%c%c%c=%c\n",
4232                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4233                 PieceToChar(PromoPiece(moveType)));
4234         break;
4235       case WhiteDrop:
4236       case BlackDrop:
4237         sprintf(user_move, "%c@%c%c\n",
4238                 ToUpper(PieceToChar((ChessSquare) fromX)),
4239                 AAA + toX, ONE + toY);
4240         break;
4241       case NormalMove:
4242       case WhiteCapturesEnPassant:
4243       case BlackCapturesEnPassant:
4244       case IllegalMove:  /* could be a variant we don't quite understand */
4245         sprintf(user_move, "%c%c%c%c\n",
4246                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4247         break;
4248     }
4249     SendToICS(user_move);
4250     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4251         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4252 }
4253
4254 void
4255 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4256      int rf, ff, rt, ft;
4257      char promoChar;
4258      char move[7];
4259 {
4260     if (rf == DROP_RANK) {
4261         sprintf(move, "%c@%c%c\n",
4262                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4263     } else {
4264         if (promoChar == 'x' || promoChar == NULLCHAR) {
4265             sprintf(move, "%c%c%c%c\n",
4266                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4267         } else {
4268             sprintf(move, "%c%c%c%c%c\n",
4269                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4270         }
4271     }
4272 }
4273
4274 void
4275 ProcessICSInitScript(f)
4276      FILE *f;
4277 {
4278     char buf[MSG_SIZ];
4279
4280     while (fgets(buf, MSG_SIZ, f)) {
4281         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4282     }
4283
4284     fclose(f);
4285 }
4286
4287
4288 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4289 void
4290 AlphaRank(char *move, int n)
4291 {
4292 //    char *p = move, c; int x, y;
4293
4294     if (appData.debugMode) {
4295         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4296     }
4297
4298     if(move[1]=='*' && 
4299        move[2]>='0' && move[2]<='9' &&
4300        move[3]>='a' && move[3]<='x'    ) {
4301         move[1] = '@';
4302         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4303         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4304     } else
4305     if(move[0]>='0' && move[0]<='9' &&
4306        move[1]>='a' && move[1]<='x' &&
4307        move[2]>='0' && move[2]<='9' &&
4308        move[3]>='a' && move[3]<='x'    ) {
4309         /* input move, Shogi -> normal */
4310         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4311         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4312         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4313         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4314     } else
4315     if(move[1]=='@' &&
4316        move[3]>='0' && move[3]<='9' &&
4317        move[2]>='a' && move[2]<='x'    ) {
4318         move[1] = '*';
4319         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4320         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4321     } else
4322     if(
4323        move[0]>='a' && move[0]<='x' &&
4324        move[3]>='0' && move[3]<='9' &&
4325        move[2]>='a' && move[2]<='x'    ) {
4326          /* output move, normal -> Shogi */
4327         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4328         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4329         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4330         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4331         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4332     }
4333     if (appData.debugMode) {
4334         fprintf(debugFP, "   out = '%s'\n", move);
4335     }
4336 }
4337
4338 /* Parser for moves from gnuchess, ICS, or user typein box */
4339 Boolean
4340 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4341      char *move;
4342      int moveNum;
4343      ChessMove *moveType;
4344      int *fromX, *fromY, *toX, *toY;
4345      char *promoChar;
4346 {       
4347     if (appData.debugMode) {
4348         fprintf(debugFP, "move to parse: %s\n", move);
4349     }
4350     *moveType = yylexstr(moveNum, move);
4351
4352     switch (*moveType) {
4353       case WhitePromotionChancellor:
4354       case BlackPromotionChancellor:
4355       case WhitePromotionArchbishop:
4356       case BlackPromotionArchbishop:
4357       case WhitePromotionQueen:
4358       case BlackPromotionQueen:
4359       case WhitePromotionRook:
4360       case BlackPromotionRook:
4361       case WhitePromotionBishop:
4362       case BlackPromotionBishop:
4363       case WhitePromotionKnight:
4364       case BlackPromotionKnight:
4365       case WhitePromotionKing:
4366       case BlackPromotionKing:
4367       case NormalMove:
4368       case WhiteCapturesEnPassant:
4369       case BlackCapturesEnPassant:
4370       case WhiteKingSideCastle:
4371       case WhiteQueenSideCastle:
4372       case BlackKingSideCastle:
4373       case BlackQueenSideCastle:
4374       case WhiteKingSideCastleWild:
4375       case WhiteQueenSideCastleWild:
4376       case BlackKingSideCastleWild:
4377       case BlackQueenSideCastleWild:
4378       /* Code added by Tord: */
4379       case WhiteHSideCastleFR:
4380       case WhiteASideCastleFR:
4381       case BlackHSideCastleFR:
4382       case BlackASideCastleFR:
4383       /* End of code added by Tord */
4384       case IllegalMove:         /* bug or odd chess variant */
4385         *fromX = currentMoveString[0] - AAA;
4386         *fromY = currentMoveString[1] - ONE;
4387         *toX = currentMoveString[2] - AAA;
4388         *toY = currentMoveString[3] - ONE;
4389         *promoChar = currentMoveString[4];
4390         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4391             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4392     if (appData.debugMode) {
4393         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4394     }
4395             *fromX = *fromY = *toX = *toY = 0;
4396             return FALSE;
4397         }
4398         if (appData.testLegality) {
4399           return (*moveType != IllegalMove);
4400         } else {
4401           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4402                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4403         }
4404
4405       case WhiteDrop:
4406       case BlackDrop:
4407         *fromX = *moveType == WhiteDrop ?
4408           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4409           (int) CharToPiece(ToLower(currentMoveString[0]));
4410         *fromY = DROP_RANK;
4411         *toX = currentMoveString[2] - AAA;
4412         *toY = currentMoveString[3] - ONE;
4413         *promoChar = NULLCHAR;
4414         return TRUE;
4415
4416       case AmbiguousMove:
4417       case ImpossibleMove:
4418       case (ChessMove) 0:       /* end of file */
4419       case ElapsedTime:
4420       case Comment:
4421       case PGNTag:
4422       case NAG:
4423       case WhiteWins:
4424       case BlackWins:
4425       case GameIsDrawn:
4426       default:
4427     if (appData.debugMode) {
4428         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4429     }
4430         /* bug? */
4431         *fromX = *fromY = *toX = *toY = 0;
4432         *promoChar = NULLCHAR;
4433         return FALSE;
4434     }
4435 }
4436
4437
4438 void
4439 ParsePV(char *pv)
4440 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4441   int fromX, fromY, toX, toY; char promoChar;
4442   ChessMove moveType;
4443   Boolean valid;
4444   int nr = 0;
4445
4446   endPV = forwardMostMove;
4447   do {
4448     while(*pv == ' ') pv++;
4449     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4450     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4451 if(appData.debugMode){
4452 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4453 }
4454     if(!valid && nr == 0 &&
4455        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4456         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4457     }
4458     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4459     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4460     nr++;
4461     if(endPV+1 > framePtr) break; // no space, truncate
4462     if(!valid) break;
4463     endPV++;
4464     CopyBoard(boards[endPV], boards[endPV-1]);
4465     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4466     moveList[endPV-1][0] = fromX + AAA;
4467     moveList[endPV-1][1] = fromY + ONE;
4468     moveList[endPV-1][2] = toX + AAA;
4469     moveList[endPV-1][3] = toY + ONE;
4470     parseList[endPV-1][0] = NULLCHAR;
4471   } while(valid);
4472   currentMove = endPV;
4473   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4474   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4475                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4476   DrawPosition(TRUE, boards[currentMove]);
4477 }
4478
4479 static int lastX, lastY;
4480
4481 Boolean
4482 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4483 {
4484         int startPV;
4485
4486         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4487         lastX = x; lastY = y;
4488         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4489         startPV = index;
4490       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4491       index = startPV;
4492         while(buf[index] && buf[index] != '\n') index++;
4493         buf[index] = 0;
4494         ParsePV(buf+startPV);
4495         *start = startPV; *end = index-1;
4496         return TRUE;
4497 }
4498
4499 Boolean
4500 LoadPV(int x, int y)
4501 { // called on right mouse click to load PV
4502   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4503   lastX = x; lastY = y;
4504   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4505   return TRUE;
4506 }
4507
4508 void
4509 UnLoadPV()
4510 {
4511   if(endPV < 0) return;
4512   endPV = -1;
4513   currentMove = forwardMostMove;
4514   ClearPremoveHighlights();
4515   DrawPosition(TRUE, boards[currentMove]);
4516 }
4517
4518 void
4519 MovePV(int x, int y, int h)
4520 { // step through PV based on mouse coordinates (called on mouse move)
4521   int margin = h>>3, step = 0;
4522
4523   if(endPV < 0) return;
4524   // we must somehow check if right button is still down (might be released off board!)
4525   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4526   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4527   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4528   if(!step) return;
4529   lastX = x; lastY = y;
4530   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4531   currentMove += step;
4532   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4533   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4534                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4535   DrawPosition(FALSE, boards[currentMove]);
4536 }
4537
4538
4539 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4540 // All positions will have equal probability, but the current method will not provide a unique
4541 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4542 #define DARK 1
4543 #define LITE 2
4544 #define ANY 3
4545
4546 int squaresLeft[4];
4547 int piecesLeft[(int)BlackPawn];
4548 int seed, nrOfShuffles;
4549
4550 void GetPositionNumber()
4551 {       // sets global variable seed
4552         int i;
4553
4554         seed = appData.defaultFrcPosition;
4555         if(seed < 0) { // randomize based on time for negative FRC position numbers
4556                 for(i=0; i<50; i++) seed += random();
4557                 seed = random() ^ random() >> 8 ^ random() << 8;
4558                 if(seed<0) seed = -seed;
4559         }
4560 }
4561
4562 int put(Board board, int pieceType, int rank, int n, int shade)
4563 // put the piece on the (n-1)-th empty squares of the given shade
4564 {
4565         int i;
4566
4567         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4568                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4569                         board[rank][i] = (ChessSquare) pieceType;
4570                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4571                         squaresLeft[ANY]--;
4572                         piecesLeft[pieceType]--; 
4573                         return i;
4574                 }
4575         }
4576         return -1;
4577 }
4578
4579
4580 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4581 // calculate where the next piece goes, (any empty square), and put it there
4582 {
4583         int i;
4584
4585         i = seed % squaresLeft[shade];
4586         nrOfShuffles *= squaresLeft[shade];
4587         seed /= squaresLeft[shade];
4588         put(board, pieceType, rank, i, shade);
4589 }
4590
4591 void AddTwoPieces(Board board, int pieceType, int rank)
4592 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4593 {
4594         int i, n=squaresLeft[ANY], j=n-1, k;
4595
4596         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4597         i = seed % k;  // pick one
4598         nrOfShuffles *= k;
4599         seed /= k;
4600         while(i >= j) i -= j--;
4601         j = n - 1 - j; i += j;
4602         put(board, pieceType, rank, j, ANY);
4603         put(board, pieceType, rank, i, ANY);
4604 }
4605
4606 void SetUpShuffle(Board board, int number)
4607 {
4608         int i, p, first=1;
4609
4610         GetPositionNumber(); nrOfShuffles = 1;
4611
4612         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4613         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4614         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4615
4616         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4617
4618         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4619             p = (int) board[0][i];
4620             if(p < (int) BlackPawn) piecesLeft[p] ++;
4621             board[0][i] = EmptySquare;
4622         }
4623
4624         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4625             // shuffles restricted to allow normal castling put KRR first
4626             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4627                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4628             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4629                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4630             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4631                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4632             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4633                 put(board, WhiteRook, 0, 0, ANY);
4634             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4635         }
4636
4637         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4638             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4639             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4640                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4641                 while(piecesLeft[p] >= 2) {
4642                     AddOnePiece(board, p, 0, LITE);
4643                     AddOnePiece(board, p, 0, DARK);
4644                 }
4645                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4646             }
4647
4648         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4649             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4650             // but we leave King and Rooks for last, to possibly obey FRC restriction
4651             if(p == (int)WhiteRook) continue;
4652             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4653             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4654         }
4655
4656         // now everything is placed, except perhaps King (Unicorn) and Rooks
4657
4658         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4659             // Last King gets castling rights
4660             while(piecesLeft[(int)WhiteUnicorn]) {
4661                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4662                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4663             }
4664
4665             while(piecesLeft[(int)WhiteKing]) {
4666                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4667                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4668             }
4669
4670
4671         } else {
4672             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4673             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4674         }
4675
4676         // Only Rooks can be left; simply place them all
4677         while(piecesLeft[(int)WhiteRook]) {
4678                 i = put(board, WhiteRook, 0, 0, ANY);
4679                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4680                         if(first) {
4681                                 first=0;
4682                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4683                         }
4684                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4685                 }
4686         }
4687         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4688             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4689         }
4690
4691         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4692 }
4693
4694 int SetCharTable( char *table, const char * map )
4695 /* [HGM] moved here from winboard.c because of its general usefulness */
4696 /*       Basically a safe strcpy that uses the last character as King */
4697 {
4698     int result = FALSE; int NrPieces;
4699
4700     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4701                     && NrPieces >= 12 && !(NrPieces&1)) {
4702         int i; /* [HGM] Accept even length from 12 to 34 */
4703
4704         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4705         for( i=0; i<NrPieces/2-1; i++ ) {
4706             table[i] = map[i];
4707             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4708         }
4709         table[(int) WhiteKing]  = map[NrPieces/2-1];
4710         table[(int) BlackKing]  = map[NrPieces-1];
4711
4712         result = TRUE;
4713     }
4714
4715     return result;
4716 }
4717
4718 void Prelude(Board board)
4719 {       // [HGM] superchess: random selection of exo-pieces
4720         int i, j, k; ChessSquare p; 
4721         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4722
4723         GetPositionNumber(); // use FRC position number
4724
4725         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4726             SetCharTable(pieceToChar, appData.pieceToCharTable);
4727             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4728                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4729         }
4730
4731         j = seed%4;                 seed /= 4; 
4732         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4733         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4734         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4735         j = seed%3 + (seed%3 >= j); seed /= 3; 
4736         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4737         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4738         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4739         j = seed%3;                 seed /= 3; 
4740         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4741         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4742         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4743         j = seed%2 + (seed%2 >= j); seed /= 2; 
4744         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4745         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4746         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4747         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4748         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4749         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4750         put(board, exoPieces[0],    0, 0, ANY);
4751         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4752 }
4753
4754 void
4755 InitPosition(redraw)
4756      int redraw;
4757 {
4758     ChessSquare (* pieces)[BOARD_FILES];
4759     int i, j, pawnRow, overrule,
4760     oldx = gameInfo.boardWidth,
4761     oldy = gameInfo.boardHeight,
4762     oldh = gameInfo.holdingsWidth,
4763     oldv = gameInfo.variant;
4764
4765     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4766
4767     /* [AS] Initialize pv info list [HGM] and game status */
4768     {
4769         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4770             pvInfoList[i].depth = 0;
4771             boards[i][EP_STATUS] = EP_NONE;
4772             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4773         }
4774
4775         initialRulePlies = 0; /* 50-move counter start */
4776
4777         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4778         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4779     }
4780
4781     
4782     /* [HGM] logic here is completely changed. In stead of full positions */
4783     /* the initialized data only consist of the two backranks. The switch */
4784     /* selects which one we will use, which is than copied to the Board   */
4785     /* initialPosition, which for the rest is initialized by Pawns and    */
4786     /* empty squares. This initial position is then copied to boards[0],  */
4787     /* possibly after shuffling, so that it remains available.            */
4788
4789     gameInfo.holdingsWidth = 0; /* default board sizes */
4790     gameInfo.boardWidth    = 8;
4791     gameInfo.boardHeight   = 8;
4792     gameInfo.holdingsSize  = 0;
4793     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4794     for(i=0; i<BOARD_FILES-2; i++)
4795       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4796     initialPosition[EP_STATUS] = EP_NONE;
4797     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4798
4799     switch (gameInfo.variant) {
4800     case VariantFischeRandom:
4801       shuffleOpenings = TRUE;
4802     default:
4803       pieces = FIDEArray;
4804       break;
4805     case VariantShatranj:
4806       pieces = ShatranjArray;
4807       nrCastlingRights = 0;
4808       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4809       break;
4810     case VariantMakruk:
4811       pieces = makrukArray;
4812       nrCastlingRights = 0;
4813       startedFromSetupPosition = TRUE;
4814       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4815       break;
4816     case VariantTwoKings:
4817       pieces = twoKingsArray;
4818       break;
4819     case VariantCapaRandom:
4820       shuffleOpenings = TRUE;
4821     case VariantCapablanca:
4822       pieces = CapablancaArray;
4823       gameInfo.boardWidth = 10;
4824       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4825       break;
4826     case VariantGothic:
4827       pieces = GothicArray;
4828       gameInfo.boardWidth = 10;
4829       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4830       break;
4831     case VariantJanus:
4832       pieces = JanusArray;
4833       gameInfo.boardWidth = 10;
4834       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4835       nrCastlingRights = 6;
4836         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4837         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4838         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4839         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4840         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4841         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4842       break;
4843     case VariantFalcon:
4844       pieces = FalconArray;
4845       gameInfo.boardWidth = 10;
4846       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4847       break;
4848     case VariantXiangqi:
4849       pieces = XiangqiArray;
4850       gameInfo.boardWidth  = 9;
4851       gameInfo.boardHeight = 10;
4852       nrCastlingRights = 0;
4853       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4854       break;
4855     case VariantShogi:
4856       pieces = ShogiArray;
4857       gameInfo.boardWidth  = 9;
4858       gameInfo.boardHeight = 9;
4859       gameInfo.holdingsSize = 7;
4860       nrCastlingRights = 0;
4861       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4862       break;
4863     case VariantCourier:
4864       pieces = CourierArray;
4865       gameInfo.boardWidth  = 12;
4866       nrCastlingRights = 0;
4867       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4868       break;
4869     case VariantKnightmate:
4870       pieces = KnightmateArray;
4871       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4872       break;
4873     case VariantFairy:
4874       pieces = fairyArray;
4875       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4876       break;
4877     case VariantGreat:
4878       pieces = GreatArray;
4879       gameInfo.boardWidth = 10;
4880       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4881       gameInfo.holdingsSize = 8;
4882       break;
4883     case VariantSuper:
4884       pieces = FIDEArray;
4885       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4886       gameInfo.holdingsSize = 8;
4887       startedFromSetupPosition = TRUE;
4888       break;
4889     case VariantCrazyhouse:
4890     case VariantBughouse:
4891       pieces = FIDEArray;
4892       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4893       gameInfo.holdingsSize = 5;
4894       break;
4895     case VariantWildCastle:
4896       pieces = FIDEArray;
4897       /* !!?shuffle with kings guaranteed to be on d or e file */
4898       shuffleOpenings = 1;
4899       break;
4900     case VariantNoCastle:
4901       pieces = FIDEArray;
4902       nrCastlingRights = 0;
4903       /* !!?unconstrained back-rank shuffle */
4904       shuffleOpenings = 1;
4905       break;
4906     }
4907
4908     overrule = 0;
4909     if(appData.NrFiles >= 0) {
4910         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4911         gameInfo.boardWidth = appData.NrFiles;
4912     }
4913     if(appData.NrRanks >= 0) {
4914         gameInfo.boardHeight = appData.NrRanks;
4915     }
4916     if(appData.holdingsSize >= 0) {
4917         i = appData.holdingsSize;
4918         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4919         gameInfo.holdingsSize = i;
4920     }
4921     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4922     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4923         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4924
4925     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4926     if(pawnRow < 1) pawnRow = 1;
4927     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4928
4929     /* User pieceToChar list overrules defaults */
4930     if(appData.pieceToCharTable != NULL)
4931         SetCharTable(pieceToChar, appData.pieceToCharTable);
4932
4933     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4934
4935         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4936             s = (ChessSquare) 0; /* account holding counts in guard band */
4937         for( i=0; i<BOARD_HEIGHT; i++ )
4938             initialPosition[i][j] = s;
4939
4940         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4941         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4942         initialPosition[pawnRow][j] = WhitePawn;
4943         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4944         if(gameInfo.variant == VariantXiangqi) {
4945             if(j&1) {
4946                 initialPosition[pawnRow][j] = 
4947                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4948                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4949                    initialPosition[2][j] = WhiteCannon;
4950                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4951                 }
4952             }
4953         }
4954         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4955     }
4956     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4957
4958             j=BOARD_LEFT+1;
4959             initialPosition[1][j] = WhiteBishop;
4960             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4961             j=BOARD_RGHT-2;
4962             initialPosition[1][j] = WhiteRook;
4963             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4964     }
4965
4966     if( nrCastlingRights == -1) {
4967         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4968         /*       This sets default castling rights from none to normal corners   */
4969         /* Variants with other castling rights must set them themselves above    */
4970         nrCastlingRights = 6;
4971        
4972         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4973         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4974         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4975         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4976         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4977         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4978      }
4979
4980      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4981      if(gameInfo.variant == VariantGreat) { // promotion commoners
4982         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4983         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4984         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4985         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4986      }
4987   if (appData.debugMode) {
4988     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4989   }
4990     if(shuffleOpenings) {
4991         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4992         startedFromSetupPosition = TRUE;
4993     }
4994     if(startedFromPositionFile) {
4995       /* [HGM] loadPos: use PositionFile for every new game */
4996       CopyBoard(initialPosition, filePosition);
4997       for(i=0; i<nrCastlingRights; i++)
4998           initialRights[i] = filePosition[CASTLING][i];
4999       startedFromSetupPosition = TRUE;
5000     }
5001
5002     CopyBoard(boards[0], initialPosition);
5003
5004     if(oldx != gameInfo.boardWidth ||
5005        oldy != gameInfo.boardHeight ||
5006        oldh != gameInfo.holdingsWidth
5007 #ifdef GOTHIC
5008        || oldv == VariantGothic ||        // For licensing popups
5009        gameInfo.variant == VariantGothic
5010 #endif
5011 #ifdef FALCON
5012        || oldv == VariantFalcon ||
5013        gameInfo.variant == VariantFalcon
5014 #endif
5015                                          )
5016             InitDrawingSizes(-2 ,0);
5017
5018     if (redraw)
5019       DrawPosition(TRUE, boards[currentMove]);
5020 }
5021
5022 void
5023 SendBoard(cps, moveNum)
5024      ChessProgramState *cps;
5025      int moveNum;
5026 {
5027     char message[MSG_SIZ];
5028     
5029     if (cps->useSetboard) {
5030       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5031       sprintf(message, "setboard %s\n", fen);
5032       SendToProgram(message, cps);
5033       free(fen);
5034
5035     } else {
5036       ChessSquare *bp;
5037       int i, j;
5038       /* Kludge to set black to move, avoiding the troublesome and now
5039        * deprecated "black" command.
5040        */
5041       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5042
5043       SendToProgram("edit\n", cps);
5044       SendToProgram("#\n", cps);
5045       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5046         bp = &boards[moveNum][i][BOARD_LEFT];
5047         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5048           if ((int) *bp < (int) BlackPawn) {
5049             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5050                     AAA + j, ONE + i);
5051             if(message[0] == '+' || message[0] == '~') {
5052                 sprintf(message, "%c%c%c+\n",
5053                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5054                         AAA + j, ONE + i);
5055             }
5056             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5057                 message[1] = BOARD_RGHT   - 1 - j + '1';
5058                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5059             }
5060             SendToProgram(message, cps);
5061           }
5062         }
5063       }
5064     
5065       SendToProgram("c\n", cps);
5066       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5067         bp = &boards[moveNum][i][BOARD_LEFT];
5068         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5069           if (((int) *bp != (int) EmptySquare)
5070               && ((int) *bp >= (int) BlackPawn)) {
5071             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5072                     AAA + j, ONE + i);
5073             if(message[0] == '+' || message[0] == '~') {
5074                 sprintf(message, "%c%c%c+\n",
5075                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5076                         AAA + j, ONE + i);
5077             }
5078             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5079                 message[1] = BOARD_RGHT   - 1 - j + '1';
5080                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5081             }
5082             SendToProgram(message, cps);
5083           }
5084         }
5085       }
5086     
5087       SendToProgram(".\n", cps);
5088     }
5089     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5090 }
5091
5092 int
5093 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5094 {
5095     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5096     /* [HGM] add Shogi promotions */
5097     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5098     ChessSquare piece;
5099     ChessMove moveType;
5100     Boolean premove;
5101
5102     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5103     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5104
5105     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5106       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5107         return FALSE;
5108
5109     piece = boards[currentMove][fromY][fromX];
5110     if(gameInfo.variant == VariantShogi) {
5111         promotionZoneSize = 3;
5112         highestPromotingPiece = (int)WhiteFerz;
5113     } else if(gameInfo.variant == VariantMakruk) {
5114         promotionZoneSize = 3;
5115     }
5116
5117     // next weed out all moves that do not touch the promotion zone at all
5118     if((int)piece >= BlackPawn) {
5119         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5120              return FALSE;
5121         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5122     } else {
5123         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5124            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5125     }
5126
5127     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5128
5129     // weed out mandatory Shogi promotions
5130     if(gameInfo.variant == VariantShogi) {
5131         if(piece >= BlackPawn) {
5132             if(toY == 0 && piece == BlackPawn ||
5133                toY == 0 && piece == BlackQueen ||
5134                toY <= 1 && piece == BlackKnight) {
5135                 *promoChoice = '+';
5136                 return FALSE;
5137             }
5138         } else {
5139             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5140                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5141                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5142                 *promoChoice = '+';
5143                 return FALSE;
5144             }
5145         }
5146     }
5147
5148     // weed out obviously illegal Pawn moves
5149     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5150         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5151         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5152         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5153         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5154         // note we are not allowed to test for valid (non-)capture, due to premove
5155     }
5156
5157     // we either have a choice what to promote to, or (in Shogi) whether to promote
5158     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5159         *promoChoice = PieceToChar(BlackFerz);  // no choice
5160         return FALSE;
5161     }
5162     if(appData.alwaysPromoteToQueen) { // predetermined
5163         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5164              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5165         else *promoChoice = PieceToChar(BlackQueen);
5166         return FALSE;
5167     }
5168
5169     // suppress promotion popup on illegal moves that are not premoves
5170     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5171               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5172     if(appData.testLegality && !premove) {
5173         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5174                         fromY, fromX, toY, toX, NULLCHAR);
5175         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5176            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5177             return FALSE;
5178     }
5179
5180     return TRUE;
5181 }
5182
5183 int
5184 InPalace(row, column)
5185      int row, column;
5186 {   /* [HGM] for Xiangqi */
5187     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5188          column < (BOARD_WIDTH + 4)/2 &&
5189          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5190     return FALSE;
5191 }
5192
5193 int
5194 PieceForSquare (x, y)
5195      int x;
5196      int y;
5197 {
5198   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5199      return -1;
5200   else
5201      return boards[currentMove][y][x];
5202 }
5203
5204 int
5205 OKToStartUserMove(x, y)
5206      int x, y;
5207 {
5208     ChessSquare from_piece;
5209     int white_piece;
5210
5211     if (matchMode) return FALSE;
5212     if (gameMode == EditPosition) return TRUE;
5213
5214     if (x >= 0 && y >= 0)
5215       from_piece = boards[currentMove][y][x];
5216     else
5217       from_piece = EmptySquare;
5218
5219     if (from_piece == EmptySquare) return FALSE;
5220
5221     white_piece = (int)from_piece >= (int)WhitePawn &&
5222       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5223
5224     switch (gameMode) {
5225       case PlayFromGameFile:
5226       case AnalyzeFile:
5227       case TwoMachinesPlay:
5228       case EndOfGame:
5229         return FALSE;
5230
5231       case IcsObserving:
5232       case IcsIdle:
5233         return FALSE;
5234
5235       case MachinePlaysWhite:
5236       case IcsPlayingBlack:
5237         if (appData.zippyPlay) return FALSE;
5238         if (white_piece) {
5239             DisplayMoveError(_("You are playing Black"));
5240             return FALSE;
5241         }
5242         break;
5243
5244       case MachinePlaysBlack:
5245       case IcsPlayingWhite:
5246         if (appData.zippyPlay) return FALSE;
5247         if (!white_piece) {
5248             DisplayMoveError(_("You are playing White"));
5249             return FALSE;
5250         }
5251         break;
5252
5253       case EditGame:
5254         if (!white_piece && WhiteOnMove(currentMove)) {
5255             DisplayMoveError(_("It is White's turn"));
5256             return FALSE;
5257         }           
5258         if (white_piece && !WhiteOnMove(currentMove)) {
5259             DisplayMoveError(_("It is Black's turn"));
5260             return FALSE;
5261         }           
5262         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5263             /* Editing correspondence game history */
5264             /* Could disallow this or prompt for confirmation */
5265             cmailOldMove = -1;
5266         }
5267         break;
5268
5269       case BeginningOfGame:
5270         if (appData.icsActive) return FALSE;
5271         if (!appData.noChessProgram) {
5272             if (!white_piece) {
5273                 DisplayMoveError(_("You are playing White"));
5274                 return FALSE;
5275             }
5276         }
5277         break;
5278         
5279       case Training:
5280         if (!white_piece && WhiteOnMove(currentMove)) {
5281             DisplayMoveError(_("It is White's turn"));
5282             return FALSE;
5283         }           
5284         if (white_piece && !WhiteOnMove(currentMove)) {
5285             DisplayMoveError(_("It is Black's turn"));
5286             return FALSE;
5287         }           
5288         break;
5289
5290       default:
5291       case IcsExamining:
5292         break;
5293     }
5294     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5295         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5296         && gameMode != AnalyzeFile && gameMode != Training) {
5297         DisplayMoveError(_("Displayed position is not current"));
5298         return FALSE;
5299     }
5300     return TRUE;
5301 }
5302
5303 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5304 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5305 int lastLoadGameUseList = FALSE;
5306 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5307 ChessMove lastLoadGameStart = (ChessMove) 0;
5308
5309 ChessMove
5310 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5311      int fromX, fromY, toX, toY;
5312      int promoChar;
5313      Boolean captureOwn;
5314 {
5315     ChessMove moveType;
5316     ChessSquare pdown, pup;
5317
5318     /* Check if the user is playing in turn.  This is complicated because we
5319        let the user "pick up" a piece before it is his turn.  So the piece he
5320        tried to pick up may have been captured by the time he puts it down!
5321        Therefore we use the color the user is supposed to be playing in this
5322        test, not the color of the piece that is currently on the starting
5323        square---except in EditGame mode, where the user is playing both
5324        sides; fortunately there the capture race can't happen.  (It can
5325        now happen in IcsExamining mode, but that's just too bad.  The user
5326        will get a somewhat confusing message in that case.)
5327        */
5328
5329     switch (gameMode) {
5330       case PlayFromGameFile:
5331       case AnalyzeFile:
5332       case TwoMachinesPlay:
5333       case EndOfGame:
5334       case IcsObserving:
5335       case IcsIdle:
5336         /* We switched into a game mode where moves are not accepted,
5337            perhaps while the mouse button was down. */
5338         return ImpossibleMove;
5339
5340       case MachinePlaysWhite:
5341         /* User is moving for Black */
5342         if (WhiteOnMove(currentMove)) {
5343             DisplayMoveError(_("It is White's turn"));
5344             return ImpossibleMove;
5345         }
5346         break;
5347
5348       case MachinePlaysBlack:
5349         /* User is moving for White */
5350         if (!WhiteOnMove(currentMove)) {
5351             DisplayMoveError(_("It is Black's turn"));
5352             return ImpossibleMove;
5353         }
5354         break;
5355
5356       case EditGame:
5357       case IcsExamining:
5358       case BeginningOfGame:
5359       case AnalyzeMode:
5360       case Training:
5361         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5362             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5363             /* User is moving for Black */
5364             if (WhiteOnMove(currentMove)) {
5365                 DisplayMoveError(_("It is White's turn"));
5366                 return ImpossibleMove;
5367             }
5368         } else {
5369             /* User is moving for White */
5370             if (!WhiteOnMove(currentMove)) {
5371                 DisplayMoveError(_("It is Black's turn"));
5372                 return ImpossibleMove;
5373             }
5374         }
5375         break;
5376
5377       case IcsPlayingBlack:
5378         /* User is moving for Black */
5379         if (WhiteOnMove(currentMove)) {
5380             if (!appData.premove) {
5381                 DisplayMoveError(_("It is White's turn"));
5382             } else if (toX >= 0 && toY >= 0) {
5383                 premoveToX = toX;
5384                 premoveToY = toY;
5385                 premoveFromX = fromX;
5386                 premoveFromY = fromY;
5387                 premovePromoChar = promoChar;
5388                 gotPremove = 1;
5389                 if (appData.debugMode) 
5390                     fprintf(debugFP, "Got premove: fromX %d,"
5391                             "fromY %d, toX %d, toY %d\n",
5392                             fromX, fromY, toX, toY);
5393             }
5394             return ImpossibleMove;
5395         }
5396         break;
5397
5398       case IcsPlayingWhite:
5399         /* User is moving for White */
5400         if (!WhiteOnMove(currentMove)) {
5401             if (!appData.premove) {
5402                 DisplayMoveError(_("It is Black's turn"));
5403             } else if (toX >= 0 && toY >= 0) {
5404                 premoveToX = toX;
5405                 premoveToY = toY;
5406                 premoveFromX = fromX;
5407                 premoveFromY = fromY;
5408                 premovePromoChar = promoChar;
5409                 gotPremove = 1;
5410                 if (appData.debugMode) 
5411                     fprintf(debugFP, "Got premove: fromX %d,"
5412                             "fromY %d, toX %d, toY %d\n",
5413                             fromX, fromY, toX, toY);
5414             }
5415             return ImpossibleMove;
5416         }
5417         break;
5418
5419       default:
5420         break;
5421
5422       case EditPosition:
5423         /* EditPosition, empty square, or different color piece;
5424            click-click move is possible */
5425         if (toX == -2 || toY == -2) {
5426             boards[0][fromY][fromX] = EmptySquare;
5427             return AmbiguousMove;
5428         } else if (toX >= 0 && toY >= 0) {
5429             boards[0][toY][toX] = boards[0][fromY][fromX];
5430             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5431                 if(boards[0][fromY][0] != EmptySquare) {
5432                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5433                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5434                 }
5435             } else
5436             if(fromX == BOARD_RGHT+1) {
5437                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5438                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5439                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5440                 }
5441             } else
5442             boards[0][fromY][fromX] = EmptySquare;
5443             return AmbiguousMove;
5444         }
5445         return ImpossibleMove;
5446     }
5447
5448     if(toX < 0 || toY < 0) return ImpossibleMove;
5449     pdown = boards[currentMove][fromY][fromX];
5450     pup = boards[currentMove][toY][toX];
5451
5452     /* [HGM] If move started in holdings, it means a drop */
5453     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5454          if( pup != EmptySquare ) return ImpossibleMove;
5455          if(appData.testLegality) {
5456              /* it would be more logical if LegalityTest() also figured out
5457               * which drops are legal. For now we forbid pawns on back rank.
5458               * Shogi is on its own here...
5459               */
5460              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5461                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5462                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5463          }
5464          return WhiteDrop; /* Not needed to specify white or black yet */
5465     }
5466
5467     /* [HGM] always test for legality, to get promotion info */
5468     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5469                                          fromY, fromX, toY, toX, promoChar);
5470     /* [HGM] but possibly ignore an IllegalMove result */
5471     if (appData.testLegality) {
5472         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5473             DisplayMoveError(_("Illegal move"));
5474             return ImpossibleMove;
5475         }
5476     }
5477
5478     return moveType;
5479     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5480        function is made into one that returns an OK move type if FinishMove
5481        should be called. This to give the calling driver routine the
5482        opportunity to finish the userMove input with a promotion popup,
5483        without bothering the user with this for invalid or illegal moves */
5484
5485 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5486 }
5487
5488 /* Common tail of UserMoveEvent and DropMenuEvent */
5489 int
5490 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5491      ChessMove moveType;
5492      int fromX, fromY, toX, toY;
5493      /*char*/int promoChar;
5494 {
5495     char *bookHit = 0;
5496
5497     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5498         // [HGM] superchess: suppress promotions to non-available piece
5499         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5500         if(WhiteOnMove(currentMove)) {
5501             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5502         } else {
5503             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5504         }
5505     }
5506
5507     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5508        move type in caller when we know the move is a legal promotion */
5509     if(moveType == NormalMove && promoChar)
5510         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5511
5512     /* [HGM] convert drag-and-drop piece drops to standard form */
5513     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5514          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5515            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5516                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5517            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5518            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5519            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5520            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5521          fromY = DROP_RANK;
5522     }
5523
5524     /* [HGM] <popupFix> The following if has been moved here from
5525        UserMoveEvent(). Because it seemed to belong here (why not allow
5526        piece drops in training games?), and because it can only be
5527        performed after it is known to what we promote. */
5528     if (gameMode == Training) {
5529       /* compare the move played on the board to the next move in the
5530        * game. If they match, display the move and the opponent's response. 
5531        * If they don't match, display an error message.
5532        */
5533       int saveAnimate;
5534       Board testBoard;
5535       CopyBoard(testBoard, boards[currentMove]);
5536       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5537
5538       if (CompareBoards(testBoard, boards[currentMove+1])) {
5539         ForwardInner(currentMove+1);
5540
5541         /* Autoplay the opponent's response.
5542          * if appData.animate was TRUE when Training mode was entered,
5543          * the response will be animated.
5544          */
5545         saveAnimate = appData.animate;
5546         appData.animate = animateTraining;
5547         ForwardInner(currentMove+1);
5548         appData.animate = saveAnimate;
5549
5550         /* check for the end of the game */
5551         if (currentMove >= forwardMostMove) {
5552           gameMode = PlayFromGameFile;
5553           ModeHighlight();
5554           SetTrainingModeOff();
5555           DisplayInformation(_("End of game"));
5556         }
5557       } else {
5558         DisplayError(_("Incorrect move"), 0);
5559       }
5560       return 1;
5561     }
5562
5563   /* Ok, now we know that the move is good, so we can kill
5564      the previous line in Analysis Mode */
5565   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5566                                 && currentMove < forwardMostMove) {
5567     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5568   }
5569
5570   /* If we need the chess program but it's dead, restart it */
5571   ResurrectChessProgram();
5572
5573   /* A user move restarts a paused game*/
5574   if (pausing)
5575     PauseEvent();
5576
5577   thinkOutput[0] = NULLCHAR;
5578
5579   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5580
5581   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5582
5583   if (gameMode == BeginningOfGame) {
5584     if (appData.noChessProgram) {
5585       gameMode = EditGame;
5586       SetGameInfo();
5587     } else {
5588       char buf[MSG_SIZ];
5589       gameMode = MachinePlaysBlack;
5590       StartClocks();
5591       SetGameInfo();
5592       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5593       DisplayTitle(buf);
5594       if (first.sendName) {
5595         sprintf(buf, "name %s\n", gameInfo.white);
5596         SendToProgram(buf, &first);
5597       }
5598       StartClocks();
5599     }
5600     ModeHighlight();
5601   }
5602
5603   /* Relay move to ICS or chess engine */
5604   if (appData.icsActive) {
5605     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5606         gameMode == IcsExamining) {
5607       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5608         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5609         SendToICS("draw ");
5610         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5611       }
5612       // also send plain move, in case ICS does not understand atomic claims
5613       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5614       ics_user_moved = 1;
5615     }
5616   } else {
5617     if (first.sendTime && (gameMode == BeginningOfGame ||
5618                            gameMode == MachinePlaysWhite ||
5619                            gameMode == MachinePlaysBlack)) {
5620       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5621     }
5622     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5623          // [HGM] book: if program might be playing, let it use book
5624         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5625         first.maybeThinking = TRUE;
5626     } else SendMoveToProgram(forwardMostMove-1, &first);
5627     if (currentMove == cmailOldMove + 1) {
5628       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5629     }
5630   }
5631
5632   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5633
5634   switch (gameMode) {
5635   case EditGame:
5636     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5637     case MT_NONE:
5638     case MT_CHECK:
5639       break;
5640     case MT_CHECKMATE:
5641     case MT_STAINMATE:
5642       if (WhiteOnMove(currentMove)) {
5643         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5644       } else {
5645         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5646       }
5647       break;
5648     case MT_STALEMATE:
5649       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5650       break;
5651     }
5652     break;
5653     
5654   case MachinePlaysBlack:
5655   case MachinePlaysWhite:
5656     /* disable certain menu options while machine is thinking */
5657     SetMachineThinkingEnables();
5658     break;
5659
5660   default:
5661     break;
5662   }
5663
5664   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5665         
5666   if(bookHit) { // [HGM] book: simulate book reply
5667         static char bookMove[MSG_SIZ]; // a bit generous?
5668
5669         programStats.nodes = programStats.depth = programStats.time = 
5670         programStats.score = programStats.got_only_move = 0;
5671         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5672
5673         strcpy(bookMove, "move ");
5674         strcat(bookMove, bookHit);
5675         HandleMachineMove(bookMove, &first);
5676   }
5677   return 1;
5678 }
5679
5680 void
5681 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5682      int fromX, fromY, toX, toY;
5683      int promoChar;
5684 {
5685     /* [HGM] This routine was added to allow calling of its two logical
5686        parts from other modules in the old way. Before, UserMoveEvent()
5687        automatically called FinishMove() if the move was OK, and returned
5688        otherwise. I separated the two, in order to make it possible to
5689        slip a promotion popup in between. But that it always needs two
5690        calls, to the first part, (now called UserMoveTest() ), and to
5691        FinishMove if the first part succeeded. Calls that do not need
5692        to do anything in between, can call this routine the old way. 
5693     */
5694     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5695 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5696     if(moveType == AmbiguousMove)
5697         DrawPosition(FALSE, boards[currentMove]);
5698     else if(moveType != ImpossibleMove && moveType != Comment)
5699         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5700 }
5701
5702 void
5703 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5704      Board board;
5705      int flags;
5706      ChessMove kind;
5707      int rf, ff, rt, ft;
5708      VOIDSTAR closure;
5709 {
5710     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5711     Markers *m = (Markers *) closure;
5712     if(rf == fromY && ff == fromX)
5713         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5714                          || kind == WhiteCapturesEnPassant
5715                          || kind == BlackCapturesEnPassant);
5716     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5717 }
5718
5719 void
5720 MarkTargetSquares(int clear)
5721 {
5722   int x, y;
5723   if(!appData.markers || !appData.highlightDragging || 
5724      !appData.testLegality || gameMode == EditPosition) return;
5725   if(clear) {
5726     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5727   } else {
5728     int capt = 0;
5729     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5730     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5731       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5732       if(capt)
5733       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5734     }
5735   }
5736   DrawPosition(TRUE, NULL);
5737 }
5738
5739 void LeftClick(ClickType clickType, int xPix, int yPix)
5740 {
5741     int x, y;
5742     Boolean saveAnimate;
5743     static int second = 0, promotionChoice = 0;
5744     char promoChoice = NULLCHAR;
5745
5746     if (clickType == Press) ErrorPopDown();
5747     MarkTargetSquares(1);
5748
5749     x = EventToSquare(xPix, BOARD_WIDTH);
5750     y = EventToSquare(yPix, BOARD_HEIGHT);
5751     if (!flipView && y >= 0) {
5752         y = BOARD_HEIGHT - 1 - y;
5753     }
5754     if (flipView && x >= 0) {
5755         x = BOARD_WIDTH - 1 - x;
5756     }
5757
5758     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5759         if(clickType == Release) return; // ignore upclick of click-click destination
5760         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5761         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5762         if(gameInfo.holdingsWidth && 
5763                 (WhiteOnMove(currentMove) 
5764                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5765                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5766             // click in right holdings, for determining promotion piece
5767             ChessSquare p = boards[currentMove][y][x];
5768             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5769             if(p != EmptySquare) {
5770                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5771                 fromX = fromY = -1;
5772                 return;
5773             }
5774         }
5775         DrawPosition(FALSE, boards[currentMove]);
5776         return;
5777     }
5778
5779     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5780     if(clickType == Press
5781             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5782               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5783               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5784         return;
5785
5786     if (fromX == -1) {
5787         if (clickType == Press) {
5788             /* First square */
5789             if (OKToStartUserMove(x, y)) {
5790                 fromX = x;
5791                 fromY = y;
5792                 second = 0;
5793                 MarkTargetSquares(0);
5794                 DragPieceBegin(xPix, yPix);
5795                 if (appData.highlightDragging) {
5796                     SetHighlights(x, y, -1, -1);
5797                 }
5798             }
5799         }
5800         return;
5801     }
5802
5803     /* fromX != -1 */
5804     if (clickType == Press && gameMode != EditPosition) {
5805         ChessSquare fromP;
5806         ChessSquare toP;
5807         int frc;
5808
5809         // ignore off-board to clicks
5810         if(y < 0 || x < 0) return;
5811
5812         /* Check if clicking again on the same color piece */
5813         fromP = boards[currentMove][fromY][fromX];
5814         toP = boards[currentMove][y][x];
5815         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5816         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5817              WhitePawn <= toP && toP <= WhiteKing &&
5818              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5819              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5820             (BlackPawn <= fromP && fromP <= BlackKing && 
5821              BlackPawn <= toP && toP <= BlackKing &&
5822              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5823              !(fromP == BlackKing && toP == BlackRook && frc))) {
5824             /* Clicked again on same color piece -- changed his mind */
5825             second = (x == fromX && y == fromY);
5826             if (appData.highlightDragging) {
5827                 SetHighlights(x, y, -1, -1);
5828             } else {
5829                 ClearHighlights();
5830             }
5831             if (OKToStartUserMove(x, y)) {
5832                 fromX = x;
5833                 fromY = y;
5834                 MarkTargetSquares(0);
5835                 DragPieceBegin(xPix, yPix);
5836             }
5837             return;
5838         }
5839         // ignore clicks on holdings
5840         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5841     }
5842
5843     if (clickType == Release && x == fromX && y == fromY) {
5844         DragPieceEnd(xPix, yPix);
5845         if (appData.animateDragging) {
5846             /* Undo animation damage if any */
5847             DrawPosition(FALSE, NULL);
5848         }
5849         if (second) {
5850             /* Second up/down in same square; just abort move */
5851             second = 0;
5852             fromX = fromY = -1;
5853             ClearHighlights();
5854             gotPremove = 0;
5855             ClearPremoveHighlights();
5856         } else {
5857             /* First upclick in same square; start click-click mode */
5858             SetHighlights(x, y, -1, -1);
5859         }
5860         return;
5861     }
5862
5863     /* we now have a different from- and (possibly off-board) to-square */
5864     /* Completed move */
5865     toX = x;
5866     toY = y;
5867     saveAnimate = appData.animate;
5868     if (clickType == Press) {
5869         /* Finish clickclick move */
5870         if (appData.animate || appData.highlightLastMove) {
5871             SetHighlights(fromX, fromY, toX, toY);
5872         } else {
5873             ClearHighlights();
5874         }
5875     } else {
5876         /* Finish drag move */
5877         if (appData.highlightLastMove) {
5878             SetHighlights(fromX, fromY, toX, toY);
5879         } else {
5880             ClearHighlights();
5881         }
5882         DragPieceEnd(xPix, yPix);
5883         /* Don't animate move and drag both */
5884         appData.animate = FALSE;
5885     }
5886
5887     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5888     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5889         ChessSquare piece = boards[currentMove][fromY][fromX];
5890         if(gameMode == EditPosition && piece != EmptySquare &&
5891            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5892             int n;
5893              
5894             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5895                 n = PieceToNumber(piece - (int)BlackPawn);
5896                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5897                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5898                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5899             } else
5900             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5901                 n = PieceToNumber(piece);
5902                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5903                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5904                 boards[currentMove][n][BOARD_WIDTH-2]++;
5905             }
5906             boards[currentMove][fromY][fromX] = EmptySquare;
5907         }
5908         ClearHighlights();
5909         fromX = fromY = -1;
5910         DrawPosition(TRUE, boards[currentMove]);
5911         return;
5912     }
5913
5914     // off-board moves should not be highlighted
5915     if(x < 0 || x < 0) ClearHighlights();
5916
5917     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5918         SetHighlights(fromX, fromY, toX, toY);
5919         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5920             // [HGM] super: promotion to captured piece selected from holdings
5921             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5922             promotionChoice = TRUE;
5923             // kludge follows to temporarily execute move on display, without promoting yet
5924             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5925             boards[currentMove][toY][toX] = p;
5926             DrawPosition(FALSE, boards[currentMove]);
5927             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5928             boards[currentMove][toY][toX] = q;
5929             DisplayMessage("Click in holdings to choose piece", "");
5930             return;
5931         }
5932         PromotionPopUp();
5933     } else {
5934         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5935         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5936         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5937         fromX = fromY = -1;
5938     }
5939     appData.animate = saveAnimate;
5940     if (appData.animate || appData.animateDragging) {
5941         /* Undo animation damage if needed */
5942         DrawPosition(FALSE, NULL);
5943     }
5944 }
5945
5946 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
5947 {   // front-end-free part taken out of PieceMenuPopup
5948     int whichMenu; int xSqr, ySqr;
5949
5950     xSqr = EventToSquare(x, BOARD_WIDTH);
5951     ySqr = EventToSquare(y, BOARD_HEIGHT);
5952     if (action == Release) UnLoadPV(); // [HGM] pv
5953     if (action != Press) return -2;
5954     switch (gameMode) {
5955       case IcsExamining:
5956         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
5957       case EditPosition:
5958         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
5959         if (xSqr < 0 || ySqr < 0) return -1;\r
5960         whichMenu = 0; // edit-position menu
5961         break;
5962       case IcsObserving:
5963         if(!appData.icsEngineAnalyze) return -1;
5964       case IcsPlayingWhite:
5965       case IcsPlayingBlack:
5966         if(!appData.zippyPlay) goto noZip;
5967       case AnalyzeMode:
5968       case AnalyzeFile:
5969       case MachinePlaysWhite:
5970       case MachinePlaysBlack:
5971       case TwoMachinesPlay: // [HGM] pv: use for showing PV
5972         if (!appData.dropMenu) {
5973           LoadPV(x, y);
5974           return 2; // flag front-end to grab mouse events
5975         }
5976         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
5977            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
5978       case EditGame:
5979       noZip:
5980         if (xSqr < 0 || ySqr < 0) return -1;
5981         if (!appData.dropMenu || appData.testLegality &&
5982             gameInfo.variant != VariantBughouse &&
5983             gameInfo.variant != VariantCrazyhouse) return -1;
5984         whichMenu = 1; // drop menu
5985         break;
5986       default:
5987         return -1;
5988     }
5989
5990     if (((*fromX = xSqr) < 0) ||
5991         ((*fromY = ySqr) < 0)) {
5992         *fromX = *fromY = -1;
5993         return -1;
5994     }
5995     if (flipView)
5996       *fromX = BOARD_WIDTH - 1 - *fromX;
5997     else
5998       *fromY = BOARD_HEIGHT - 1 - *fromY;
5999
6000     return whichMenu;
6001 }
6002
6003 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6004 {
6005 //    char * hint = lastHint;
6006     FrontEndProgramStats stats;
6007
6008     stats.which = cps == &first ? 0 : 1;
6009     stats.depth = cpstats->depth;
6010     stats.nodes = cpstats->nodes;
6011     stats.score = cpstats->score;
6012     stats.time = cpstats->time;
6013     stats.pv = cpstats->movelist;
6014     stats.hint = lastHint;
6015     stats.an_move_index = 0;
6016     stats.an_move_count = 0;
6017
6018     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6019         stats.hint = cpstats->move_name;
6020         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6021         stats.an_move_count = cpstats->nr_moves;
6022     }
6023
6024     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6025
6026     SetProgramStats( &stats );
6027 }
6028
6029 int
6030 Adjudicate(ChessProgramState *cps)
6031 {       // [HGM] some adjudications useful with buggy engines
6032         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6033         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6034         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6035         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6036         int k, count = 0; static int bare = 1;
6037         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6038         Boolean canAdjudicate = !appData.icsActive;
6039
6040         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6041         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6042             if( appData.testLegality )
6043             {   /* [HGM] Some more adjudications for obstinate engines */
6044                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6045                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6046                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6047                 static int moveCount = 6;
6048                 ChessMove result;
6049                 char *reason = NULL;
6050
6051                 /* Count what is on board. */
6052                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6053                 {   ChessSquare p = boards[forwardMostMove][i][j];
6054                     int m=i;
6055
6056                     switch((int) p)
6057                     {   /* count B,N,R and other of each side */
6058                         case WhiteKing:
6059                         case BlackKing:
6060                              NrK++; break; // [HGM] atomic: count Kings
6061                         case WhiteKnight:
6062                              NrWN++; break;
6063                         case WhiteBishop:
6064                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6065                              bishopsColor |= 1 << ((i^j)&1);
6066                              NrWB++; break;
6067                         case BlackKnight:
6068                              NrBN++; break;
6069                         case BlackBishop:
6070                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6071                              bishopsColor |= 1 << ((i^j)&1);
6072                              NrBB++; break;
6073                         case WhiteRook:
6074                              NrWR++; break;
6075                         case BlackRook:
6076                              NrBR++; break;
6077                         case WhiteQueen:
6078                              NrWQ++; break;
6079                         case BlackQueen:
6080                              NrBQ++; break;
6081                         case EmptySquare: 
6082                              break;
6083                         case BlackPawn:
6084                              m = 7-i;
6085                         case WhitePawn:
6086                              PawnAdvance += m; NrPawns++;
6087                     }
6088                     NrPieces += (p != EmptySquare);
6089                     NrW += ((int)p < (int)BlackPawn);
6090                     if(gameInfo.variant == VariantXiangqi && 
6091                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6092                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6093                         NrW -= ((int)p < (int)BlackPawn);
6094                     }
6095                 }
6096
6097                 /* Some material-based adjudications that have to be made before stalemate test */
6098                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6099                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6100                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6101                      if(canAdjudicate && appData.checkMates) {
6102                          if(engineOpponent)
6103                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6104                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6105                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6106                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6107                          return 1;
6108                      }
6109                 }
6110
6111                 /* Bare King in Shatranj (loses) or Losers (wins) */
6112                 if( NrW == 1 || NrPieces - NrW == 1) {
6113                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6114                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6115                      if(canAdjudicate && appData.checkMates) {
6116                          if(engineOpponent)
6117                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6118                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6119                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6120                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6121                          return 1;
6122                      }
6123                   } else
6124                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6125                   {    /* bare King */
6126                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6127                         if(canAdjudicate && appData.checkMates) {
6128                             /* but only adjudicate if adjudication enabled */
6129                             if(engineOpponent)
6130                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6131                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6132                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6133                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6134                             return 1;
6135                         }
6136                   }
6137                 } else bare = 1;
6138
6139
6140             // don't wait for engine to announce game end if we can judge ourselves
6141             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6142               case MT_CHECK:
6143                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6144                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6145                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6146                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6147                             checkCnt++;
6148                         if(checkCnt >= 2) {
6149                             reason = "Xboard adjudication: 3rd check";
6150                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6151                             break;
6152                         }
6153                     }
6154                 }
6155               case MT_NONE:
6156               default:
6157                 break;
6158               case MT_STALEMATE:
6159               case MT_STAINMATE:
6160                 reason = "Xboard adjudication: Stalemate";
6161                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6162                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6163                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6164                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6165                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6166                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6167                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6168                                                                         EP_CHECKMATE : EP_WINS);
6169                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6170                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6171                 }
6172                 break;
6173               case MT_CHECKMATE:
6174                 reason = "Xboard adjudication: Checkmate";
6175                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6176                 break;
6177             }
6178
6179                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6180                     case EP_STALEMATE:
6181                         result = GameIsDrawn; break;
6182                     case EP_CHECKMATE:
6183                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6184                     case EP_WINS:
6185                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6186                     default:
6187                         result = (ChessMove) 0;
6188                 }
6189                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6190                     if(engineOpponent)
6191                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6192                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6193                     GameEnds( result, reason, GE_XBOARD );
6194                     return 1;
6195                 }
6196
6197                 /* Next absolutely insufficient mating material. */
6198                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6199                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6200                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6201                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6202                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6203
6204                      /* always flag draws, for judging claims */
6205                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6206
6207                      if(canAdjudicate && appData.materialDraws) {
6208                          /* but only adjudicate them if adjudication enabled */
6209                          if(engineOpponent) {
6210                            SendToProgram("force\n", engineOpponent); // suppress reply
6211                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6212                          }
6213                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6214                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6215                          return 1;
6216                      }
6217                 }
6218
6219                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6220                 if(NrPieces == 4 && 
6221                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6222                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6223                    || NrWN==2 || NrBN==2     /* KNNK */
6224                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6225                   ) ) {
6226                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6227                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6228                           if(engineOpponent) {
6229                             SendToProgram("force\n", engineOpponent); // suppress reply
6230                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6231                           }
6232                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6233                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6234                           return 1;
6235                      }
6236                 } else moveCount = 6;
6237             }
6238         }
6239           
6240         if (appData.debugMode) { int i;
6241             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6242                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6243                     appData.drawRepeats);
6244             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6245               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6246             
6247         }
6248
6249         // Repetition draws and 50-move rule can be applied independently of legality testing
6250
6251                 /* Check for rep-draws */
6252                 count = 0;
6253                 for(k = forwardMostMove-2;
6254                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6255                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6256                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6257                     k-=2)
6258                 {   int rights=0;
6259                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6260                         /* compare castling rights */
6261                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6262                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6263                                 rights++; /* King lost rights, while rook still had them */
6264                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6265                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6266                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6267                                    rights++; /* but at least one rook lost them */
6268                         }
6269                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6270                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6271                                 rights++; 
6272                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6273                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6274                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6275                                    rights++;
6276                         }
6277                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6278                             && appData.drawRepeats > 1) {
6279                              /* adjudicate after user-specified nr of repeats */
6280                              if(engineOpponent) {
6281                                SendToProgram("force\n", engineOpponent); // suppress reply
6282                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6283                              }
6284                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6285                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6286                                 // [HGM] xiangqi: check for forbidden perpetuals
6287                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6288                                 for(m=forwardMostMove; m>k; m-=2) {
6289                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6290                                         ourPerpetual = 0; // the current mover did not always check
6291                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6292                                         hisPerpetual = 0; // the opponent did not always check
6293                                 }
6294                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6295                                                                         ourPerpetual, hisPerpetual);
6296                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6297                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6298                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6299                                     return 1;
6300                                 }
6301                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6302                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6303                                 // Now check for perpetual chases
6304                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6305                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6306                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6307                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6308                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6309                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6310                                         return 1;
6311                                     }
6312                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6313                                         break; // Abort repetition-checking loop.
6314                                 }
6315                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6316                              }
6317                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6318                              return 1;
6319                         }
6320                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6321                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6322                     }
6323                 }
6324
6325                 /* Now we test for 50-move draws. Determine ply count */
6326                 count = forwardMostMove;
6327                 /* look for last irreversble move */
6328                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6329                     count--;
6330                 /* if we hit starting position, add initial plies */
6331                 if( count == backwardMostMove )
6332                     count -= initialRulePlies;
6333                 count = forwardMostMove - count; 
6334                 if( count >= 100)
6335                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6336                          /* this is used to judge if draw claims are legal */
6337                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6338                          if(engineOpponent) {
6339                            SendToProgram("force\n", engineOpponent); // suppress reply
6340                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6341                          }
6342                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6343                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6344                          return 1;
6345                 }
6346
6347                 /* if draw offer is pending, treat it as a draw claim
6348                  * when draw condition present, to allow engines a way to
6349                  * claim draws before making their move to avoid a race
6350                  * condition occurring after their move
6351                  */
6352                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6353                          char *p = NULL;
6354                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6355                              p = "Draw claim: 50-move rule";
6356                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6357                              p = "Draw claim: 3-fold repetition";
6358                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6359                              p = "Draw claim: insufficient mating material";
6360                          if( p != NULL && canAdjudicate) {
6361                              if(engineOpponent) {
6362                                SendToProgram("force\n", engineOpponent); // suppress reply
6363                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6364                              }
6365                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6366                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6367                              return 1;
6368                          }
6369                 }
6370
6371                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6372                     if(engineOpponent) {
6373                       SendToProgram("force\n", engineOpponent); // suppress reply
6374                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6375                     }
6376                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6377                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6378                     return 1;
6379                 }
6380         return 0;
6381 }
6382
6383 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6384 {   // [HGM] book: this routine intercepts moves to simulate book replies
6385     char *bookHit = NULL;
6386
6387     //first determine if the incoming move brings opponent into his book
6388     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6389         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6390     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6391     if(bookHit != NULL && !cps->bookSuspend) {
6392         // make sure opponent is not going to reply after receiving move to book position
6393         SendToProgram("force\n", cps);
6394         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6395     }
6396     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6397     // now arrange restart after book miss
6398     if(bookHit) {
6399         // after a book hit we never send 'go', and the code after the call to this routine
6400         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6401         char buf[MSG_SIZ];
6402         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6403         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6404         SendToProgram(buf, cps);
6405         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6406     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6407         SendToProgram("go\n", cps);
6408         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6409     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6410         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6411             SendToProgram("go\n", cps); 
6412         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6413     }
6414     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6415 }
6416
6417 char *savedMessage;
6418 ChessProgramState *savedState;
6419 void DeferredBookMove(void)
6420 {
6421         if(savedState->lastPing != savedState->lastPong)
6422                     ScheduleDelayedEvent(DeferredBookMove, 10);
6423         else
6424         HandleMachineMove(savedMessage, savedState);
6425 }
6426
6427 void
6428 HandleMachineMove(message, cps)
6429      char *message;
6430      ChessProgramState *cps;
6431 {
6432     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6433     char realname[MSG_SIZ];
6434     int fromX, fromY, toX, toY;
6435     ChessMove moveType;
6436     char promoChar;
6437     char *p;
6438     int machineWhite;
6439     char *bookHit;
6440
6441     cps->userError = 0;
6442
6443 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6444     /*
6445      * Kludge to ignore BEL characters
6446      */
6447     while (*message == '\007') message++;
6448
6449     /*
6450      * [HGM] engine debug message: ignore lines starting with '#' character
6451      */
6452     if(cps->debug && *message == '#') return;
6453
6454     /*
6455      * Look for book output
6456      */
6457     if (cps == &first && bookRequested) {
6458         if (message[0] == '\t' || message[0] == ' ') {
6459             /* Part of the book output is here; append it */
6460             strcat(bookOutput, message);
6461             strcat(bookOutput, "  \n");
6462             return;
6463         } else if (bookOutput[0] != NULLCHAR) {
6464             /* All of book output has arrived; display it */
6465             char *p = bookOutput;
6466             while (*p != NULLCHAR) {
6467                 if (*p == '\t') *p = ' ';
6468                 p++;
6469             }
6470             DisplayInformation(bookOutput);
6471             bookRequested = FALSE;
6472             /* Fall through to parse the current output */
6473         }
6474     }
6475
6476     /*
6477      * Look for machine move.
6478      */
6479     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6480         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6481     {
6482         /* This method is only useful on engines that support ping */
6483         if (cps->lastPing != cps->lastPong) {
6484           if (gameMode == BeginningOfGame) {
6485             /* Extra move from before last new; ignore */
6486             if (appData.debugMode) {
6487                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6488             }
6489           } else {
6490             if (appData.debugMode) {
6491                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6492                         cps->which, gameMode);
6493             }
6494
6495             SendToProgram("undo\n", cps);
6496           }
6497           return;
6498         }
6499
6500         switch (gameMode) {
6501           case BeginningOfGame:
6502             /* Extra move from before last reset; ignore */
6503             if (appData.debugMode) {
6504                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6505             }
6506             return;
6507
6508           case EndOfGame:
6509           case IcsIdle:
6510           default:
6511             /* Extra move after we tried to stop.  The mode test is
6512                not a reliable way of detecting this problem, but it's
6513                the best we can do on engines that don't support ping.
6514             */
6515             if (appData.debugMode) {
6516                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6517                         cps->which, gameMode);
6518             }
6519             SendToProgram("undo\n", cps);
6520             return;
6521
6522           case MachinePlaysWhite:
6523           case IcsPlayingWhite:
6524             machineWhite = TRUE;
6525             break;
6526
6527           case MachinePlaysBlack:
6528           case IcsPlayingBlack:
6529             machineWhite = FALSE;
6530             break;
6531
6532           case TwoMachinesPlay:
6533             machineWhite = (cps->twoMachinesColor[0] == 'w');
6534             break;
6535         }
6536         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6537             if (appData.debugMode) {
6538                 fprintf(debugFP,
6539                         "Ignoring move out of turn by %s, gameMode %d"
6540                         ", forwardMost %d\n",
6541                         cps->which, gameMode, forwardMostMove);
6542             }
6543             return;
6544         }
6545
6546     if (appData.debugMode) { int f = forwardMostMove;
6547         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6548                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6549                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6550     }
6551         if(cps->alphaRank) AlphaRank(machineMove, 4);
6552         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6553                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6554             /* Machine move could not be parsed; ignore it. */
6555             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6556                     machineMove, cps->which);
6557             DisplayError(buf1, 0);
6558             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6559                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6560             if (gameMode == TwoMachinesPlay) {
6561               GameEnds(machineWhite ? BlackWins : WhiteWins,
6562                        buf1, GE_XBOARD);
6563             }
6564             return;
6565         }
6566
6567         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6568         /* So we have to redo legality test with true e.p. status here,  */
6569         /* to make sure an illegal e.p. capture does not slip through,   */
6570         /* to cause a forfeit on a justified illegal-move complaint      */
6571         /* of the opponent.                                              */
6572         if( gameMode==TwoMachinesPlay && appData.testLegality
6573             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6574                                                               ) {
6575            ChessMove moveType;
6576            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6577                              fromY, fromX, toY, toX, promoChar);
6578             if (appData.debugMode) {
6579                 int i;
6580                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6581                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6582                 fprintf(debugFP, "castling rights\n");
6583             }
6584             if(moveType == IllegalMove) {
6585                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6586                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6587                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6588                            buf1, GE_XBOARD);
6589                 return;
6590            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6591            /* [HGM] Kludge to handle engines that send FRC-style castling
6592               when they shouldn't (like TSCP-Gothic) */
6593            switch(moveType) {
6594              case WhiteASideCastleFR:
6595              case BlackASideCastleFR:
6596                toX+=2;
6597                currentMoveString[2]++;
6598                break;
6599              case WhiteHSideCastleFR:
6600              case BlackHSideCastleFR:
6601                toX--;
6602                currentMoveString[2]--;
6603                break;
6604              default: ; // nothing to do, but suppresses warning of pedantic compilers
6605            }
6606         }
6607         hintRequested = FALSE;
6608         lastHint[0] = NULLCHAR;
6609         bookRequested = FALSE;
6610         /* Program may be pondering now */
6611         cps->maybeThinking = TRUE;
6612         if (cps->sendTime == 2) cps->sendTime = 1;
6613         if (cps->offeredDraw) cps->offeredDraw--;
6614
6615         /* currentMoveString is set as a side-effect of ParseOneMove */
6616         strcpy(machineMove, currentMoveString);
6617         strcat(machineMove, "\n");
6618         strcpy(moveList[forwardMostMove], machineMove);
6619
6620         /* [AS] Save move info and clear stats for next move */
6621         pvInfoList[ forwardMostMove ].score = programStats.score;
6622         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6623         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6624         ClearProgramStats();
6625         thinkOutput[0] = NULLCHAR;
6626         hiddenThinkOutputState = 0;
6627
6628         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6629
6630         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6631         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6632             int count = 0;
6633
6634             while( count < adjudicateLossPlies ) {
6635                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6636
6637                 if( count & 1 ) {
6638                     score = -score; /* Flip score for winning side */
6639                 }
6640
6641                 if( score > adjudicateLossThreshold ) {
6642                     break;
6643                 }
6644
6645                 count++;
6646             }
6647
6648             if( count >= adjudicateLossPlies ) {
6649                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6650
6651                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6652                     "Xboard adjudication", 
6653                     GE_XBOARD );
6654
6655                 return;
6656             }
6657         }
6658
6659         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6660
6661 #if ZIPPY
6662         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6663             first.initDone) {
6664           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6665                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6666                 SendToICS("draw ");
6667                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6668           }
6669           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6670           ics_user_moved = 1;
6671           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6672                 char buf[3*MSG_SIZ];
6673
6674                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6675                         programStats.score / 100.,
6676                         programStats.depth,
6677                         programStats.time / 100.,
6678                         (unsigned int)programStats.nodes,
6679                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6680                         programStats.movelist);
6681                 SendToICS(buf);
6682 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6683           }
6684         }
6685 #endif
6686
6687         bookHit = NULL;
6688         if (gameMode == TwoMachinesPlay) {
6689             /* [HGM] relaying draw offers moved to after reception of move */
6690             /* and interpreting offer as claim if it brings draw condition */
6691             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6692                 SendToProgram("draw\n", cps->other);
6693             }
6694             if (cps->other->sendTime) {
6695                 SendTimeRemaining(cps->other,
6696                                   cps->other->twoMachinesColor[0] == 'w');
6697             }
6698             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6699             if (firstMove && !bookHit) {
6700                 firstMove = FALSE;
6701                 if (cps->other->useColors) {
6702                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6703                 }
6704                 SendToProgram("go\n", cps->other);
6705             }
6706             cps->other->maybeThinking = TRUE;
6707         }
6708
6709         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6710         
6711         if (!pausing && appData.ringBellAfterMoves) {
6712             RingBell();
6713         }
6714
6715         /* 
6716          * Reenable menu items that were disabled while
6717          * machine was thinking
6718          */
6719         if (gameMode != TwoMachinesPlay)
6720             SetUserThinkingEnables();
6721
6722         // [HGM] book: after book hit opponent has received move and is now in force mode
6723         // force the book reply into it, and then fake that it outputted this move by jumping
6724         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6725         if(bookHit) {
6726                 static char bookMove[MSG_SIZ]; // a bit generous?
6727
6728                 strcpy(bookMove, "move ");
6729                 strcat(bookMove, bookHit);
6730                 message = bookMove;
6731                 cps = cps->other;
6732                 programStats.nodes = programStats.depth = programStats.time = 
6733                 programStats.score = programStats.got_only_move = 0;
6734                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6735
6736                 if(cps->lastPing != cps->lastPong) {
6737                     savedMessage = message; // args for deferred call
6738                     savedState = cps;
6739                     ScheduleDelayedEvent(DeferredBookMove, 10);
6740                     return;
6741                 }
6742                 goto FakeBookMove;
6743         }
6744
6745         return;
6746     }
6747
6748     /* Set special modes for chess engines.  Later something general
6749      *  could be added here; for now there is just one kludge feature,
6750      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6751      *  when "xboard" is given as an interactive command.
6752      */
6753     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6754         cps->useSigint = FALSE;
6755         cps->useSigterm = FALSE;
6756     }
6757     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6758       ParseFeatures(message+8, cps);
6759       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6760     }
6761
6762     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6763      * want this, I was asked to put it in, and obliged.
6764      */
6765     if (!strncmp(message, "setboard ", 9)) {
6766         Board initial_position;
6767
6768         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6769
6770         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6771             DisplayError(_("Bad FEN received from engine"), 0);
6772             return ;
6773         } else {
6774            Reset(TRUE, FALSE);
6775            CopyBoard(boards[0], initial_position);
6776            initialRulePlies = FENrulePlies;
6777            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6778            else gameMode = MachinePlaysBlack;                 
6779            DrawPosition(FALSE, boards[currentMove]);
6780         }
6781         return;
6782     }
6783
6784     /*
6785      * Look for communication commands
6786      */
6787     if (!strncmp(message, "telluser ", 9)) {
6788         DisplayNote(message + 9);
6789         return;
6790     }
6791     if (!strncmp(message, "tellusererror ", 14)) {
6792         cps->userError = 1;
6793         DisplayError(message + 14, 0);
6794         return;
6795     }
6796     if (!strncmp(message, "tellopponent ", 13)) {
6797       if (appData.icsActive) {
6798         if (loggedOn) {
6799           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6800           SendToICS(buf1);
6801         }
6802       } else {
6803         DisplayNote(message + 13);
6804       }
6805       return;
6806     }
6807     if (!strncmp(message, "tellothers ", 11)) {
6808       if (appData.icsActive) {
6809         if (loggedOn) {
6810           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6811           SendToICS(buf1);
6812         }
6813       }
6814       return;
6815     }
6816     if (!strncmp(message, "tellall ", 8)) {
6817       if (appData.icsActive) {
6818         if (loggedOn) {
6819           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6820           SendToICS(buf1);
6821         }
6822       } else {
6823         DisplayNote(message + 8);
6824       }
6825       return;
6826     }
6827     if (strncmp(message, "warning", 7) == 0) {
6828         /* Undocumented feature, use tellusererror in new code */
6829         DisplayError(message, 0);
6830         return;
6831     }
6832     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6833         strcpy(realname, cps->tidy);
6834         strcat(realname, " query");
6835         AskQuestion(realname, buf2, buf1, cps->pr);
6836         return;
6837     }
6838     /* Commands from the engine directly to ICS.  We don't allow these to be 
6839      *  sent until we are logged on. Crafty kibitzes have been known to 
6840      *  interfere with the login process.
6841      */
6842     if (loggedOn) {
6843         if (!strncmp(message, "tellics ", 8)) {
6844             SendToICS(message + 8);
6845             SendToICS("\n");
6846             return;
6847         }
6848         if (!strncmp(message, "tellicsnoalias ", 15)) {
6849             SendToICS(ics_prefix);
6850             SendToICS(message + 15);
6851             SendToICS("\n");
6852             return;
6853         }
6854         /* The following are for backward compatibility only */
6855         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6856             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6857             SendToICS(ics_prefix);
6858             SendToICS(message);
6859             SendToICS("\n");
6860             return;
6861         }
6862     }
6863     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6864         return;
6865     }
6866     /*
6867      * If the move is illegal, cancel it and redraw the board.
6868      * Also deal with other error cases.  Matching is rather loose
6869      * here to accommodate engines written before the spec.
6870      */
6871     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6872         strncmp(message, "Error", 5) == 0) {
6873         if (StrStr(message, "name") || 
6874             StrStr(message, "rating") || StrStr(message, "?") ||
6875             StrStr(message, "result") || StrStr(message, "board") ||
6876             StrStr(message, "bk") || StrStr(message, "computer") ||
6877             StrStr(message, "variant") || StrStr(message, "hint") ||
6878             StrStr(message, "random") || StrStr(message, "depth") ||
6879             StrStr(message, "accepted")) {
6880             return;
6881         }
6882         if (StrStr(message, "protover")) {
6883           /* Program is responding to input, so it's apparently done
6884              initializing, and this error message indicates it is
6885              protocol version 1.  So we don't need to wait any longer
6886              for it to initialize and send feature commands. */
6887           FeatureDone(cps, 1);
6888           cps->protocolVersion = 1;
6889           return;
6890         }
6891         cps->maybeThinking = FALSE;
6892
6893         if (StrStr(message, "draw")) {
6894             /* Program doesn't have "draw" command */
6895             cps->sendDrawOffers = 0;
6896             return;
6897         }
6898         if (cps->sendTime != 1 &&
6899             (StrStr(message, "time") || StrStr(message, "otim"))) {
6900           /* Program apparently doesn't have "time" or "otim" command */
6901           cps->sendTime = 0;
6902           return;
6903         }
6904         if (StrStr(message, "analyze")) {
6905             cps->analysisSupport = FALSE;
6906             cps->analyzing = FALSE;
6907             Reset(FALSE, TRUE);
6908             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6909             DisplayError(buf2, 0);
6910             return;
6911         }
6912         if (StrStr(message, "(no matching move)st")) {
6913           /* Special kludge for GNU Chess 4 only */
6914           cps->stKludge = TRUE;
6915           SendTimeControl(cps, movesPerSession, timeControl,
6916                           timeIncrement, appData.searchDepth,
6917                           searchTime);
6918           return;
6919         }
6920         if (StrStr(message, "(no matching move)sd")) {
6921           /* Special kludge for GNU Chess 4 only */
6922           cps->sdKludge = TRUE;
6923           SendTimeControl(cps, movesPerSession, timeControl,
6924                           timeIncrement, appData.searchDepth,
6925                           searchTime);
6926           return;
6927         }
6928         if (!StrStr(message, "llegal")) {
6929             return;
6930         }
6931         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6932             gameMode == IcsIdle) return;
6933         if (forwardMostMove <= backwardMostMove) return;
6934         if (pausing) PauseEvent();
6935       if(appData.forceIllegal) {
6936             // [HGM] illegal: machine refused move; force position after move into it
6937           SendToProgram("force\n", cps);
6938           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6939                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6940                 // when black is to move, while there might be nothing on a2 or black
6941                 // might already have the move. So send the board as if white has the move.
6942                 // But first we must change the stm of the engine, as it refused the last move
6943                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6944                 if(WhiteOnMove(forwardMostMove)) {
6945                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6946                     SendBoard(cps, forwardMostMove); // kludgeless board
6947                 } else {
6948                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6949                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6950                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6951                 }
6952           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6953             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6954                  gameMode == TwoMachinesPlay)
6955               SendToProgram("go\n", cps);
6956             return;
6957       } else
6958         if (gameMode == PlayFromGameFile) {
6959             /* Stop reading this game file */
6960             gameMode = EditGame;
6961             ModeHighlight();
6962         }
6963         currentMove = --forwardMostMove;
6964         DisplayMove(currentMove-1); /* before DisplayMoveError */
6965         SwitchClocks();
6966         DisplayBothClocks();
6967         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6968                 parseList[currentMove], cps->which);
6969         DisplayMoveError(buf1);
6970         DrawPosition(FALSE, boards[currentMove]);
6971
6972         /* [HGM] illegal-move claim should forfeit game when Xboard */
6973         /* only passes fully legal moves                            */
6974         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6975             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6976                                 "False illegal-move claim", GE_XBOARD );
6977         }
6978         return;
6979     }
6980     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6981         /* Program has a broken "time" command that
6982            outputs a string not ending in newline.
6983            Don't use it. */
6984         cps->sendTime = 0;
6985     }
6986     
6987     /*
6988      * If chess program startup fails, exit with an error message.
6989      * Attempts to recover here are futile.
6990      */
6991     if ((StrStr(message, "unknown host") != NULL)
6992         || (StrStr(message, "No remote directory") != NULL)
6993         || (StrStr(message, "not found") != NULL)
6994         || (StrStr(message, "No such file") != NULL)
6995         || (StrStr(message, "can't alloc") != NULL)
6996         || (StrStr(message, "Permission denied") != NULL)) {
6997
6998         cps->maybeThinking = FALSE;
6999         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7000                 cps->which, cps->program, cps->host, message);
7001         RemoveInputSource(cps->isr);
7002         DisplayFatalError(buf1, 0, 1);
7003         return;
7004     }
7005     
7006     /* 
7007      * Look for hint output
7008      */
7009     if (sscanf(message, "Hint: %s", buf1) == 1) {
7010         if (cps == &first && hintRequested) {
7011             hintRequested = FALSE;
7012             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7013                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7014                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7015                                     PosFlags(forwardMostMove),
7016                                     fromY, fromX, toY, toX, promoChar, buf1);
7017                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7018                 DisplayInformation(buf2);
7019             } else {
7020                 /* Hint move could not be parsed!? */
7021               snprintf(buf2, sizeof(buf2),
7022                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7023                         buf1, cps->which);
7024                 DisplayError(buf2, 0);
7025             }
7026         } else {
7027             strcpy(lastHint, buf1);
7028         }
7029         return;
7030     }
7031
7032     /*
7033      * Ignore other messages if game is not in progress
7034      */
7035     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7036         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7037
7038     /*
7039      * look for win, lose, draw, or draw offer
7040      */
7041     if (strncmp(message, "1-0", 3) == 0) {
7042         char *p, *q, *r = "";
7043         p = strchr(message, '{');
7044         if (p) {
7045             q = strchr(p, '}');
7046             if (q) {
7047                 *q = NULLCHAR;
7048                 r = p + 1;
7049             }
7050         }
7051         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7052         return;
7053     } else if (strncmp(message, "0-1", 3) == 0) {
7054         char *p, *q, *r = "";
7055         p = strchr(message, '{');
7056         if (p) {
7057             q = strchr(p, '}');
7058             if (q) {
7059                 *q = NULLCHAR;
7060                 r = p + 1;
7061             }
7062         }
7063         /* Kludge for Arasan 4.1 bug */
7064         if (strcmp(r, "Black resigns") == 0) {
7065             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7066             return;
7067         }
7068         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7069         return;
7070     } else if (strncmp(message, "1/2", 3) == 0) {
7071         char *p, *q, *r = "";
7072         p = strchr(message, '{');
7073         if (p) {
7074             q = strchr(p, '}');
7075             if (q) {
7076                 *q = NULLCHAR;
7077                 r = p + 1;
7078             }
7079         }
7080             
7081         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7082         return;
7083
7084     } else if (strncmp(message, "White resign", 12) == 0) {
7085         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7086         return;
7087     } else if (strncmp(message, "Black resign", 12) == 0) {
7088         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7089         return;
7090     } else if (strncmp(message, "White matches", 13) == 0 ||
7091                strncmp(message, "Black matches", 13) == 0   ) {
7092         /* [HGM] ignore GNUShogi noises */
7093         return;
7094     } else if (strncmp(message, "White", 5) == 0 &&
7095                message[5] != '(' &&
7096                StrStr(message, "Black") == NULL) {
7097         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7098         return;
7099     } else if (strncmp(message, "Black", 5) == 0 &&
7100                message[5] != '(') {
7101         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7102         return;
7103     } else if (strcmp(message, "resign") == 0 ||
7104                strcmp(message, "computer resigns") == 0) {
7105         switch (gameMode) {
7106           case MachinePlaysBlack:
7107           case IcsPlayingBlack:
7108             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7109             break;
7110           case MachinePlaysWhite:
7111           case IcsPlayingWhite:
7112             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7113             break;
7114           case TwoMachinesPlay:
7115             if (cps->twoMachinesColor[0] == 'w')
7116               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7117             else
7118               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7119             break;
7120           default:
7121             /* can't happen */
7122             break;
7123         }
7124         return;
7125     } else if (strncmp(message, "opponent mates", 14) == 0) {
7126         switch (gameMode) {
7127           case MachinePlaysBlack:
7128           case IcsPlayingBlack:
7129             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7130             break;
7131           case MachinePlaysWhite:
7132           case IcsPlayingWhite:
7133             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7134             break;
7135           case TwoMachinesPlay:
7136             if (cps->twoMachinesColor[0] == 'w')
7137               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7138             else
7139               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7140             break;
7141           default:
7142             /* can't happen */
7143             break;
7144         }
7145         return;
7146     } else if (strncmp(message, "computer mates", 14) == 0) {
7147         switch (gameMode) {
7148           case MachinePlaysBlack:
7149           case IcsPlayingBlack:
7150             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7151             break;
7152           case MachinePlaysWhite:
7153           case IcsPlayingWhite:
7154             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7155             break;
7156           case TwoMachinesPlay:
7157             if (cps->twoMachinesColor[0] == 'w')
7158               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7159             else
7160               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7161             break;
7162           default:
7163             /* can't happen */
7164             break;
7165         }
7166         return;
7167     } else if (strncmp(message, "checkmate", 9) == 0) {
7168         if (WhiteOnMove(forwardMostMove)) {
7169             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7170         } else {
7171             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7172         }
7173         return;
7174     } else if (strstr(message, "Draw") != NULL ||
7175                strstr(message, "game is a draw") != NULL) {
7176         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7177         return;
7178     } else if (strstr(message, "offer") != NULL &&
7179                strstr(message, "draw") != NULL) {
7180 #if ZIPPY
7181         if (appData.zippyPlay && first.initDone) {
7182             /* Relay offer to ICS */
7183             SendToICS(ics_prefix);
7184             SendToICS("draw\n");
7185         }
7186 #endif
7187         cps->offeredDraw = 2; /* valid until this engine moves twice */
7188         if (gameMode == TwoMachinesPlay) {
7189             if (cps->other->offeredDraw) {
7190                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7191             /* [HGM] in two-machine mode we delay relaying draw offer      */
7192             /* until after we also have move, to see if it is really claim */
7193             }
7194         } else if (gameMode == MachinePlaysWhite ||
7195                    gameMode == MachinePlaysBlack) {
7196           if (userOfferedDraw) {
7197             DisplayInformation(_("Machine accepts your draw offer"));
7198             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7199           } else {
7200             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7201           }
7202         }
7203     }
7204
7205     
7206     /*
7207      * Look for thinking output
7208      */
7209     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7210           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7211                                 ) {
7212         int plylev, mvleft, mvtot, curscore, time;
7213         char mvname[MOVE_LEN];
7214         u64 nodes; // [DM]
7215         char plyext;
7216         int ignore = FALSE;
7217         int prefixHint = FALSE;
7218         mvname[0] = NULLCHAR;
7219
7220         switch (gameMode) {
7221           case MachinePlaysBlack:
7222           case IcsPlayingBlack:
7223             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7224             break;
7225           case MachinePlaysWhite:
7226           case IcsPlayingWhite:
7227             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7228             break;
7229           case AnalyzeMode:
7230           case AnalyzeFile:
7231             break;
7232           case IcsObserving: /* [DM] icsEngineAnalyze */
7233             if (!appData.icsEngineAnalyze) ignore = TRUE;
7234             break;
7235           case TwoMachinesPlay:
7236             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7237                 ignore = TRUE;
7238             }
7239             break;
7240           default:
7241             ignore = TRUE;
7242             break;
7243         }
7244
7245         if (!ignore) {
7246             buf1[0] = NULLCHAR;
7247             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7248                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7249
7250                 if (plyext != ' ' && plyext != '\t') {
7251                     time *= 100;
7252                 }
7253
7254                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7255                 if( cps->scoreIsAbsolute && 
7256                     ( gameMode == MachinePlaysBlack ||
7257                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7258                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7259                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7260                      !WhiteOnMove(currentMove)
7261                     ) )
7262                 {
7263                     curscore = -curscore;
7264                 }
7265
7266
7267                 programStats.depth = plylev;
7268                 programStats.nodes = nodes;
7269                 programStats.time = time;
7270                 programStats.score = curscore;
7271                 programStats.got_only_move = 0;
7272
7273                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7274                         int ticklen;
7275
7276                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7277                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7278                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7279                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7280                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7281                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7282                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7283                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7284                 }
7285
7286                 /* Buffer overflow protection */
7287                 if (buf1[0] != NULLCHAR) {
7288                     if (strlen(buf1) >= sizeof(programStats.movelist)
7289                         && appData.debugMode) {
7290                         fprintf(debugFP,
7291                                 "PV is too long; using the first %u bytes.\n",
7292                                 (unsigned) sizeof(programStats.movelist) - 1);
7293                     }
7294
7295                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7296                 } else {
7297                     sprintf(programStats.movelist, " no PV\n");
7298                 }
7299
7300                 if (programStats.seen_stat) {
7301                     programStats.ok_to_send = 1;
7302                 }
7303
7304                 if (strchr(programStats.movelist, '(') != NULL) {
7305                     programStats.line_is_book = 1;
7306                     programStats.nr_moves = 0;
7307                     programStats.moves_left = 0;
7308                 } else {
7309                     programStats.line_is_book = 0;
7310                 }
7311
7312                 SendProgramStatsToFrontend( cps, &programStats );
7313
7314                 /* 
7315                     [AS] Protect the thinkOutput buffer from overflow... this
7316                     is only useful if buf1 hasn't overflowed first!
7317                 */
7318                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7319                         plylev, 
7320                         (gameMode == TwoMachinesPlay ?
7321                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7322                         ((double) curscore) / 100.0,
7323                         prefixHint ? lastHint : "",
7324                         prefixHint ? " " : "" );
7325
7326                 if( buf1[0] != NULLCHAR ) {
7327                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7328
7329                     if( strlen(buf1) > max_len ) {
7330                         if( appData.debugMode) {
7331                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7332                         }
7333                         buf1[max_len+1] = '\0';
7334                     }
7335
7336                     strcat( thinkOutput, buf1 );
7337                 }
7338
7339                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7340                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7341                     DisplayMove(currentMove - 1);
7342                 }
7343                 return;
7344
7345             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7346                 /* crafty (9.25+) says "(only move) <move>"
7347                  * if there is only 1 legal move
7348                  */
7349                 sscanf(p, "(only move) %s", buf1);
7350                 sprintf(thinkOutput, "%s (only move)", buf1);
7351                 sprintf(programStats.movelist, "%s (only move)", buf1);
7352                 programStats.depth = 1;
7353                 programStats.nr_moves = 1;
7354                 programStats.moves_left = 1;
7355                 programStats.nodes = 1;
7356                 programStats.time = 1;
7357                 programStats.got_only_move = 1;
7358
7359                 /* Not really, but we also use this member to
7360                    mean "line isn't going to change" (Crafty
7361                    isn't searching, so stats won't change) */
7362                 programStats.line_is_book = 1;
7363
7364                 SendProgramStatsToFrontend( cps, &programStats );
7365                 
7366                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7367                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7368                     DisplayMove(currentMove - 1);
7369                 }
7370                 return;
7371             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7372                               &time, &nodes, &plylev, &mvleft,
7373                               &mvtot, mvname) >= 5) {
7374                 /* The stat01: line is from Crafty (9.29+) in response
7375                    to the "." command */
7376                 programStats.seen_stat = 1;
7377                 cps->maybeThinking = TRUE;
7378
7379                 if (programStats.got_only_move || !appData.periodicUpdates)
7380                   return;
7381
7382                 programStats.depth = plylev;
7383                 programStats.time = time;
7384                 programStats.nodes = nodes;
7385                 programStats.moves_left = mvleft;
7386                 programStats.nr_moves = mvtot;
7387                 strcpy(programStats.move_name, mvname);
7388                 programStats.ok_to_send = 1;
7389                 programStats.movelist[0] = '\0';
7390
7391                 SendProgramStatsToFrontend( cps, &programStats );
7392
7393                 return;
7394
7395             } else if (strncmp(message,"++",2) == 0) {
7396                 /* Crafty 9.29+ outputs this */
7397                 programStats.got_fail = 2;
7398                 return;
7399
7400             } else if (strncmp(message,"--",2) == 0) {
7401                 /* Crafty 9.29+ outputs this */
7402                 programStats.got_fail = 1;
7403                 return;
7404
7405             } else if (thinkOutput[0] != NULLCHAR &&
7406                        strncmp(message, "    ", 4) == 0) {
7407                 unsigned message_len;
7408
7409                 p = message;
7410                 while (*p && *p == ' ') p++;
7411
7412                 message_len = strlen( p );
7413
7414                 /* [AS] Avoid buffer overflow */
7415                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7416                     strcat(thinkOutput, " ");
7417                     strcat(thinkOutput, p);
7418                 }
7419
7420                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7421                     strcat(programStats.movelist, " ");
7422                     strcat(programStats.movelist, p);
7423                 }
7424
7425                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7426                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7427                     DisplayMove(currentMove - 1);
7428                 }
7429                 return;
7430             }
7431         }
7432         else {
7433             buf1[0] = NULLCHAR;
7434
7435             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7436                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7437             {
7438                 ChessProgramStats cpstats;
7439
7440                 if (plyext != ' ' && plyext != '\t') {
7441                     time *= 100;
7442                 }
7443
7444                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7445                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7446                     curscore = -curscore;
7447                 }
7448
7449                 cpstats.depth = plylev;
7450                 cpstats.nodes = nodes;
7451                 cpstats.time = time;
7452                 cpstats.score = curscore;
7453                 cpstats.got_only_move = 0;
7454                 cpstats.movelist[0] = '\0';
7455
7456                 if (buf1[0] != NULLCHAR) {
7457                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7458                 }
7459
7460                 cpstats.ok_to_send = 0;
7461                 cpstats.line_is_book = 0;
7462                 cpstats.nr_moves = 0;
7463                 cpstats.moves_left = 0;
7464
7465                 SendProgramStatsToFrontend( cps, &cpstats );
7466             }
7467         }
7468     }
7469 }
7470
7471
7472 /* Parse a game score from the character string "game", and
7473    record it as the history of the current game.  The game
7474    score is NOT assumed to start from the standard position. 
7475    The display is not updated in any way.
7476    */
7477 void
7478 ParseGameHistory(game)
7479      char *game;
7480 {
7481     ChessMove moveType;
7482     int fromX, fromY, toX, toY, boardIndex;
7483     char promoChar;
7484     char *p, *q;
7485     char buf[MSG_SIZ];
7486
7487     if (appData.debugMode)
7488       fprintf(debugFP, "Parsing game history: %s\n", game);
7489
7490     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7491     gameInfo.site = StrSave(appData.icsHost);
7492     gameInfo.date = PGNDate();
7493     gameInfo.round = StrSave("-");
7494
7495     /* Parse out names of players */
7496     while (*game == ' ') game++;
7497     p = buf;
7498     while (*game != ' ') *p++ = *game++;
7499     *p = NULLCHAR;
7500     gameInfo.white = StrSave(buf);
7501     while (*game == ' ') game++;
7502     p = buf;
7503     while (*game != ' ' && *game != '\n') *p++ = *game++;
7504     *p = NULLCHAR;
7505     gameInfo.black = StrSave(buf);
7506
7507     /* Parse moves */
7508     boardIndex = blackPlaysFirst ? 1 : 0;
7509     yynewstr(game);
7510     for (;;) {
7511         yyboardindex = boardIndex;
7512         moveType = (ChessMove) yylex();
7513         switch (moveType) {
7514           case IllegalMove:             /* maybe suicide chess, etc. */
7515   if (appData.debugMode) {
7516     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7517     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7518     setbuf(debugFP, NULL);
7519   }
7520           case WhitePromotionChancellor:
7521           case BlackPromotionChancellor:
7522           case WhitePromotionArchbishop:
7523           case BlackPromotionArchbishop:
7524           case WhitePromotionQueen:
7525           case BlackPromotionQueen:
7526           case WhitePromotionRook:
7527           case BlackPromotionRook:
7528           case WhitePromotionBishop:
7529           case BlackPromotionBishop:
7530           case WhitePromotionKnight:
7531           case BlackPromotionKnight:
7532           case WhitePromotionKing:
7533           case BlackPromotionKing:
7534           case NormalMove:
7535           case WhiteCapturesEnPassant:
7536           case BlackCapturesEnPassant:
7537           case WhiteKingSideCastle:
7538           case WhiteQueenSideCastle:
7539           case BlackKingSideCastle:
7540           case BlackQueenSideCastle:
7541           case WhiteKingSideCastleWild:
7542           case WhiteQueenSideCastleWild:
7543           case BlackKingSideCastleWild:
7544           case BlackQueenSideCastleWild:
7545           /* PUSH Fabien */
7546           case WhiteHSideCastleFR:
7547           case WhiteASideCastleFR:
7548           case BlackHSideCastleFR:
7549           case BlackASideCastleFR:
7550           /* POP Fabien */
7551             fromX = currentMoveString[0] - AAA;
7552             fromY = currentMoveString[1] - ONE;
7553             toX = currentMoveString[2] - AAA;
7554             toY = currentMoveString[3] - ONE;
7555             promoChar = currentMoveString[4];
7556             break;
7557           case WhiteDrop:
7558           case BlackDrop:
7559             fromX = moveType == WhiteDrop ?
7560               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7561             (int) CharToPiece(ToLower(currentMoveString[0]));
7562             fromY = DROP_RANK;
7563             toX = currentMoveString[2] - AAA;
7564             toY = currentMoveString[3] - ONE;
7565             promoChar = NULLCHAR;
7566             break;
7567           case AmbiguousMove:
7568             /* bug? */
7569             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7570   if (appData.debugMode) {
7571     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7572     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7573     setbuf(debugFP, NULL);
7574   }
7575             DisplayError(buf, 0);
7576             return;
7577           case ImpossibleMove:
7578             /* bug? */
7579             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7580   if (appData.debugMode) {
7581     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7582     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7583     setbuf(debugFP, NULL);
7584   }
7585             DisplayError(buf, 0);
7586             return;
7587           case (ChessMove) 0:   /* end of file */
7588             if (boardIndex < backwardMostMove) {
7589                 /* Oops, gap.  How did that happen? */
7590                 DisplayError(_("Gap in move list"), 0);
7591                 return;
7592             }
7593             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7594             if (boardIndex > forwardMostMove) {
7595                 forwardMostMove = boardIndex;
7596             }
7597             return;
7598           case ElapsedTime:
7599             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7600                 strcat(parseList[boardIndex-1], " ");
7601                 strcat(parseList[boardIndex-1], yy_text);
7602             }
7603             continue;
7604           case Comment:
7605           case PGNTag:
7606           case NAG:
7607           default:
7608             /* ignore */
7609             continue;
7610           case WhiteWins:
7611           case BlackWins:
7612           case GameIsDrawn:
7613           case GameUnfinished:
7614             if (gameMode == IcsExamining) {
7615                 if (boardIndex < backwardMostMove) {
7616                     /* Oops, gap.  How did that happen? */
7617                     return;
7618                 }
7619                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7620                 return;
7621             }
7622             gameInfo.result = moveType;
7623             p = strchr(yy_text, '{');
7624             if (p == NULL) p = strchr(yy_text, '(');
7625             if (p == NULL) {
7626                 p = yy_text;
7627                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7628             } else {
7629                 q = strchr(p, *p == '{' ? '}' : ')');
7630                 if (q != NULL) *q = NULLCHAR;
7631                 p++;
7632             }
7633             gameInfo.resultDetails = StrSave(p);
7634             continue;
7635         }
7636         if (boardIndex >= forwardMostMove &&
7637             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7638             backwardMostMove = blackPlaysFirst ? 1 : 0;
7639             return;
7640         }
7641         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7642                                  fromY, fromX, toY, toX, promoChar,
7643                                  parseList[boardIndex]);
7644         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7645         /* currentMoveString is set as a side-effect of yylex */
7646         strcpy(moveList[boardIndex], currentMoveString);
7647         strcat(moveList[boardIndex], "\n");
7648         boardIndex++;
7649         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7650         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7651           case MT_NONE:
7652           case MT_STALEMATE:
7653           default:
7654             break;
7655           case MT_CHECK:
7656             if(gameInfo.variant != VariantShogi)
7657                 strcat(parseList[boardIndex - 1], "+");
7658             break;
7659           case MT_CHECKMATE:
7660           case MT_STAINMATE:
7661             strcat(parseList[boardIndex - 1], "#");
7662             break;
7663         }
7664     }
7665 }
7666
7667
7668 /* Apply a move to the given board  */
7669 void
7670 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7671      int fromX, fromY, toX, toY;
7672      int promoChar;
7673      Board board;
7674 {
7675   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7676   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7677
7678     /* [HGM] compute & store e.p. status and castling rights for new position */
7679     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7680     { int i;
7681
7682       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7683       oldEP = (signed char)board[EP_STATUS];
7684       board[EP_STATUS] = EP_NONE;
7685
7686       if( board[toY][toX] != EmptySquare ) 
7687            board[EP_STATUS] = EP_CAPTURE;  
7688
7689       if( board[fromY][fromX] == WhitePawn ) {
7690            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7691                board[EP_STATUS] = EP_PAWN_MOVE;
7692            if( toY-fromY==2) {
7693                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7694                         gameInfo.variant != VariantBerolina || toX < fromX)
7695                       board[EP_STATUS] = toX | berolina;
7696                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7697                         gameInfo.variant != VariantBerolina || toX > fromX) 
7698                       board[EP_STATUS] = toX;
7699            }
7700       } else 
7701       if( board[fromY][fromX] == BlackPawn ) {
7702            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7703                board[EP_STATUS] = EP_PAWN_MOVE; 
7704            if( toY-fromY== -2) {
7705                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7706                         gameInfo.variant != VariantBerolina || toX < fromX)
7707                       board[EP_STATUS] = toX | berolina;
7708                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7709                         gameInfo.variant != VariantBerolina || toX > fromX) 
7710                       board[EP_STATUS] = toX;
7711            }
7712        }
7713
7714        for(i=0; i<nrCastlingRights; i++) {
7715            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7716               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7717              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7718        }
7719
7720     }
7721
7722   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7723   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7724        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7725          
7726   if (fromX == toX && fromY == toY) return;
7727
7728   if (fromY == DROP_RANK) {
7729         /* must be first */
7730         piece = board[toY][toX] = (ChessSquare) fromX;
7731   } else {
7732      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7733      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7734      if(gameInfo.variant == VariantKnightmate)
7735          king += (int) WhiteUnicorn - (int) WhiteKing;
7736
7737     /* Code added by Tord: */
7738     /* FRC castling assumed when king captures friendly rook. */
7739     if (board[fromY][fromX] == WhiteKing &&
7740              board[toY][toX] == WhiteRook) {
7741       board[fromY][fromX] = EmptySquare;
7742       board[toY][toX] = EmptySquare;
7743       if(toX > fromX) {
7744         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7745       } else {
7746         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7747       }
7748     } else if (board[fromY][fromX] == BlackKing &&
7749                board[toY][toX] == BlackRook) {
7750       board[fromY][fromX] = EmptySquare;
7751       board[toY][toX] = EmptySquare;
7752       if(toX > fromX) {
7753         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7754       } else {
7755         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7756       }
7757     /* End of code added by Tord */
7758
7759     } else if (board[fromY][fromX] == king
7760         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7761         && toY == fromY && toX > fromX+1) {
7762         board[fromY][fromX] = EmptySquare;
7763         board[toY][toX] = king;
7764         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7765         board[fromY][BOARD_RGHT-1] = EmptySquare;
7766     } else if (board[fromY][fromX] == king
7767         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7768                && toY == fromY && toX < fromX-1) {
7769         board[fromY][fromX] = EmptySquare;
7770         board[toY][toX] = king;
7771         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7772         board[fromY][BOARD_LEFT] = EmptySquare;
7773     } else if (board[fromY][fromX] == WhitePawn
7774                && toY >= BOARD_HEIGHT-promoRank
7775                && gameInfo.variant != VariantXiangqi
7776                ) {
7777         /* white pawn promotion */
7778         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7779         if (board[toY][toX] == EmptySquare) {
7780             board[toY][toX] = WhiteQueen;
7781         }
7782         if(gameInfo.variant==VariantBughouse ||
7783            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7784             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7785         board[fromY][fromX] = EmptySquare;
7786     } else if ((fromY == BOARD_HEIGHT-4)
7787                && (toX != fromX)
7788                && gameInfo.variant != VariantXiangqi
7789                && gameInfo.variant != VariantBerolina
7790                && (board[fromY][fromX] == WhitePawn)
7791                && (board[toY][toX] == EmptySquare)) {
7792         board[fromY][fromX] = EmptySquare;
7793         board[toY][toX] = WhitePawn;
7794         captured = board[toY - 1][toX];
7795         board[toY - 1][toX] = EmptySquare;
7796     } else if ((fromY == BOARD_HEIGHT-4)
7797                && (toX == fromX)
7798                && gameInfo.variant == VariantBerolina
7799                && (board[fromY][fromX] == WhitePawn)
7800                && (board[toY][toX] == EmptySquare)) {
7801         board[fromY][fromX] = EmptySquare;
7802         board[toY][toX] = WhitePawn;
7803         if(oldEP & EP_BEROLIN_A) {
7804                 captured = board[fromY][fromX-1];
7805                 board[fromY][fromX-1] = EmptySquare;
7806         }else{  captured = board[fromY][fromX+1];
7807                 board[fromY][fromX+1] = EmptySquare;
7808         }
7809     } else if (board[fromY][fromX] == king
7810         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7811                && toY == fromY && toX > fromX+1) {
7812         board[fromY][fromX] = EmptySquare;
7813         board[toY][toX] = king;
7814         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7815         board[fromY][BOARD_RGHT-1] = EmptySquare;
7816     } else if (board[fromY][fromX] == king
7817         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7818                && toY == fromY && toX < fromX-1) {
7819         board[fromY][fromX] = EmptySquare;
7820         board[toY][toX] = king;
7821         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7822         board[fromY][BOARD_LEFT] = EmptySquare;
7823     } else if (fromY == 7 && fromX == 3
7824                && board[fromY][fromX] == BlackKing
7825                && toY == 7 && toX == 5) {
7826         board[fromY][fromX] = EmptySquare;
7827         board[toY][toX] = BlackKing;
7828         board[fromY][7] = EmptySquare;
7829         board[toY][4] = BlackRook;
7830     } else if (fromY == 7 && fromX == 3
7831                && board[fromY][fromX] == BlackKing
7832                && toY == 7 && toX == 1) {
7833         board[fromY][fromX] = EmptySquare;
7834         board[toY][toX] = BlackKing;
7835         board[fromY][0] = EmptySquare;
7836         board[toY][2] = BlackRook;
7837     } else if (board[fromY][fromX] == BlackPawn
7838                && toY < promoRank
7839                && gameInfo.variant != VariantXiangqi
7840                ) {
7841         /* black pawn promotion */
7842         board[toY][toX] = CharToPiece(ToLower(promoChar));
7843         if (board[toY][toX] == EmptySquare) {
7844             board[toY][toX] = BlackQueen;
7845         }
7846         if(gameInfo.variant==VariantBughouse ||
7847            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7848             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7849         board[fromY][fromX] = EmptySquare;
7850     } else if ((fromY == 3)
7851                && (toX != fromX)
7852                && gameInfo.variant != VariantXiangqi
7853                && gameInfo.variant != VariantBerolina
7854                && (board[fromY][fromX] == BlackPawn)
7855                && (board[toY][toX] == EmptySquare)) {
7856         board[fromY][fromX] = EmptySquare;
7857         board[toY][toX] = BlackPawn;
7858         captured = board[toY + 1][toX];
7859         board[toY + 1][toX] = EmptySquare;
7860     } else if ((fromY == 3)
7861                && (toX == fromX)
7862                && gameInfo.variant == VariantBerolina
7863                && (board[fromY][fromX] == BlackPawn)
7864                && (board[toY][toX] == EmptySquare)) {
7865         board[fromY][fromX] = EmptySquare;
7866         board[toY][toX] = BlackPawn;
7867         if(oldEP & EP_BEROLIN_A) {
7868                 captured = board[fromY][fromX-1];
7869                 board[fromY][fromX-1] = EmptySquare;
7870         }else{  captured = board[fromY][fromX+1];
7871                 board[fromY][fromX+1] = EmptySquare;
7872         }
7873     } else {
7874         board[toY][toX] = board[fromY][fromX];
7875         board[fromY][fromX] = EmptySquare;
7876     }
7877
7878     /* [HGM] now we promote for Shogi, if needed */
7879     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7880         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7881   }
7882
7883     if (gameInfo.holdingsWidth != 0) {
7884
7885       /* !!A lot more code needs to be written to support holdings  */
7886       /* [HGM] OK, so I have written it. Holdings are stored in the */
7887       /* penultimate board files, so they are automaticlly stored   */
7888       /* in the game history.                                       */
7889       if (fromY == DROP_RANK) {
7890         /* Delete from holdings, by decreasing count */
7891         /* and erasing image if necessary            */
7892         p = (int) fromX;
7893         if(p < (int) BlackPawn) { /* white drop */
7894              p -= (int)WhitePawn;
7895                  p = PieceToNumber((ChessSquare)p);
7896              if(p >= gameInfo.holdingsSize) p = 0;
7897              if(--board[p][BOARD_WIDTH-2] <= 0)
7898                   board[p][BOARD_WIDTH-1] = EmptySquare;
7899              if((int)board[p][BOARD_WIDTH-2] < 0)
7900                         board[p][BOARD_WIDTH-2] = 0;
7901         } else {                  /* black drop */
7902              p -= (int)BlackPawn;
7903                  p = PieceToNumber((ChessSquare)p);
7904              if(p >= gameInfo.holdingsSize) p = 0;
7905              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7906                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7907              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7908                         board[BOARD_HEIGHT-1-p][1] = 0;
7909         }
7910       }
7911       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7912           && gameInfo.variant != VariantBughouse        ) {
7913         /* [HGM] holdings: Add to holdings, if holdings exist */
7914         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7915                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7916                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7917         }
7918         p = (int) captured;
7919         if (p >= (int) BlackPawn) {
7920           p -= (int)BlackPawn;
7921           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7922                   /* in Shogi restore piece to its original  first */
7923                   captured = (ChessSquare) (DEMOTED captured);
7924                   p = DEMOTED p;
7925           }
7926           p = PieceToNumber((ChessSquare)p);
7927           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7928           board[p][BOARD_WIDTH-2]++;
7929           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7930         } else {
7931           p -= (int)WhitePawn;
7932           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7933                   captured = (ChessSquare) (DEMOTED captured);
7934                   p = DEMOTED p;
7935           }
7936           p = PieceToNumber((ChessSquare)p);
7937           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7938           board[BOARD_HEIGHT-1-p][1]++;
7939           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7940         }
7941       }
7942     } else if (gameInfo.variant == VariantAtomic) {
7943       if (captured != EmptySquare) {
7944         int y, x;
7945         for (y = toY-1; y <= toY+1; y++) {
7946           for (x = toX-1; x <= toX+1; x++) {
7947             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7948                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7949               board[y][x] = EmptySquare;
7950             }
7951           }
7952         }
7953         board[toY][toX] = EmptySquare;
7954       }
7955     }
7956     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7957         /* [HGM] Shogi promotions */
7958         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7959     }
7960
7961     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7962                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7963         // [HGM] superchess: take promotion piece out of holdings
7964         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7965         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7966             if(!--board[k][BOARD_WIDTH-2])
7967                 board[k][BOARD_WIDTH-1] = EmptySquare;
7968         } else {
7969             if(!--board[BOARD_HEIGHT-1-k][1])
7970                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7971         }
7972     }
7973
7974 }
7975
7976 /* Updates forwardMostMove */
7977 void
7978 MakeMove(fromX, fromY, toX, toY, promoChar)
7979      int fromX, fromY, toX, toY;
7980      int promoChar;
7981 {
7982 //    forwardMostMove++; // [HGM] bare: moved downstream
7983
7984     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7985         int timeLeft; static int lastLoadFlag=0; int king, piece;
7986         piece = boards[forwardMostMove][fromY][fromX];
7987         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7988         if(gameInfo.variant == VariantKnightmate)
7989             king += (int) WhiteUnicorn - (int) WhiteKing;
7990         if(forwardMostMove == 0) {
7991             if(blackPlaysFirst) 
7992                 fprintf(serverMoves, "%s;", second.tidy);
7993             fprintf(serverMoves, "%s;", first.tidy);
7994             if(!blackPlaysFirst) 
7995                 fprintf(serverMoves, "%s;", second.tidy);
7996         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7997         lastLoadFlag = loadFlag;
7998         // print base move
7999         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8000         // print castling suffix
8001         if( toY == fromY && piece == king ) {
8002             if(toX-fromX > 1)
8003                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8004             if(fromX-toX >1)
8005                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8006         }
8007         // e.p. suffix
8008         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8009              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8010              boards[forwardMostMove][toY][toX] == EmptySquare
8011              && fromX != toX )
8012                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8013         // promotion suffix
8014         if(promoChar != NULLCHAR)
8015                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8016         if(!loadFlag) {
8017             fprintf(serverMoves, "/%d/%d",
8018                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8019             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8020             else                      timeLeft = blackTimeRemaining/1000;
8021             fprintf(serverMoves, "/%d", timeLeft);
8022         }
8023         fflush(serverMoves);
8024     }
8025
8026     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8027       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8028                         0, 1);
8029       return;
8030     }
8031     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8032     if (commentList[forwardMostMove+1] != NULL) {
8033         free(commentList[forwardMostMove+1]);
8034         commentList[forwardMostMove+1] = NULL;
8035     }
8036     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8037     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8038     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8039     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8040     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8041     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8042     gameInfo.result = GameUnfinished;
8043     if (gameInfo.resultDetails != NULL) {
8044         free(gameInfo.resultDetails);
8045         gameInfo.resultDetails = NULL;
8046     }
8047     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8048                               moveList[forwardMostMove - 1]);
8049     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8050                              PosFlags(forwardMostMove - 1),
8051                              fromY, fromX, toY, toX, promoChar,
8052                              parseList[forwardMostMove - 1]);
8053     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8054       case MT_NONE:
8055       case MT_STALEMATE:
8056       default:
8057         break;
8058       case MT_CHECK:
8059         if(gameInfo.variant != VariantShogi)
8060             strcat(parseList[forwardMostMove - 1], "+");
8061         break;
8062       case MT_CHECKMATE:
8063       case MT_STAINMATE:
8064         strcat(parseList[forwardMostMove - 1], "#");
8065         break;
8066     }
8067     if (appData.debugMode) {
8068         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8069     }
8070
8071 }
8072
8073 /* Updates currentMove if not pausing */
8074 void
8075 ShowMove(fromX, fromY, toX, toY)
8076 {
8077     int instant = (gameMode == PlayFromGameFile) ?
8078         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8079     if(appData.noGUI) return;
8080     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8081         if (!instant) {
8082             if (forwardMostMove == currentMove + 1) {
8083                 AnimateMove(boards[forwardMostMove - 1],
8084                             fromX, fromY, toX, toY);
8085             }
8086             if (appData.highlightLastMove) {
8087                 SetHighlights(fromX, fromY, toX, toY);
8088             }
8089         }
8090         currentMove = forwardMostMove;
8091     }
8092
8093     if (instant) return;
8094
8095     DisplayMove(currentMove - 1);
8096     DrawPosition(FALSE, boards[currentMove]);
8097     DisplayBothClocks();
8098     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8099 }
8100
8101 void SendEgtPath(ChessProgramState *cps)
8102 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8103         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8104
8105         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8106
8107         while(*p) {
8108             char c, *q = name+1, *r, *s;
8109
8110             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8111             while(*p && *p != ',') *q++ = *p++;
8112             *q++ = ':'; *q = 0;
8113             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8114                 strcmp(name, ",nalimov:") == 0 ) {
8115                 // take nalimov path from the menu-changeable option first, if it is defined
8116                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8117                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8118             } else
8119             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8120                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8121                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8122                 s = r = StrStr(s, ":") + 1; // beginning of path info
8123                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8124                 c = *r; *r = 0;             // temporarily null-terminate path info
8125                     *--q = 0;               // strip of trailig ':' from name
8126                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8127                 *r = c;
8128                 SendToProgram(buf,cps);     // send egtbpath command for this format
8129             }
8130             if(*p == ',') p++; // read away comma to position for next format name
8131         }
8132 }
8133
8134 void
8135 InitChessProgram(cps, setup)
8136      ChessProgramState *cps;
8137      int setup; /* [HGM] needed to setup FRC opening position */
8138 {
8139     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8140     if (appData.noChessProgram) return;
8141     hintRequested = FALSE;
8142     bookRequested = FALSE;
8143
8144     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8145     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8146     if(cps->memSize) { /* [HGM] memory */
8147         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8148         SendToProgram(buf, cps);
8149     }
8150     SendEgtPath(cps); /* [HGM] EGT */
8151     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8152         sprintf(buf, "cores %d\n", appData.smpCores);
8153         SendToProgram(buf, cps);
8154     }
8155
8156     SendToProgram(cps->initString, cps);
8157     if (gameInfo.variant != VariantNormal &&
8158         gameInfo.variant != VariantLoadable
8159         /* [HGM] also send variant if board size non-standard */
8160         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8161                                             ) {
8162       char *v = VariantName(gameInfo.variant);
8163       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8164         /* [HGM] in protocol 1 we have to assume all variants valid */
8165         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8166         DisplayFatalError(buf, 0, 1);
8167         return;
8168       }
8169
8170       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8171       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8172       if( gameInfo.variant == VariantXiangqi )
8173            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8174       if( gameInfo.variant == VariantShogi )
8175            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8176       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8177            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8178       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8179                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8180            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8181       if( gameInfo.variant == VariantCourier )
8182            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8183       if( gameInfo.variant == VariantSuper )
8184            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8185       if( gameInfo.variant == VariantGreat )
8186            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8187
8188       if(overruled) {
8189            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8190                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8191            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8192            if(StrStr(cps->variants, b) == NULL) { 
8193                // specific sized variant not known, check if general sizing allowed
8194                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8195                    if(StrStr(cps->variants, "boardsize") == NULL) {
8196                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8197                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8198                        DisplayFatalError(buf, 0, 1);
8199                        return;
8200                    }
8201                    /* [HGM] here we really should compare with the maximum supported board size */
8202                }
8203            }
8204       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8205       sprintf(buf, "variant %s\n", b);
8206       SendToProgram(buf, cps);
8207     }
8208     currentlyInitializedVariant = gameInfo.variant;
8209
8210     /* [HGM] send opening position in FRC to first engine */
8211     if(setup) {
8212           SendToProgram("force\n", cps);
8213           SendBoard(cps, 0);
8214           /* engine is now in force mode! Set flag to wake it up after first move. */
8215           setboardSpoiledMachineBlack = 1;
8216     }
8217
8218     if (cps->sendICS) {
8219       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8220       SendToProgram(buf, cps);
8221     }
8222     cps->maybeThinking = FALSE;
8223     cps->offeredDraw = 0;
8224     if (!appData.icsActive) {
8225         SendTimeControl(cps, movesPerSession, timeControl,
8226                         timeIncrement, appData.searchDepth,
8227                         searchTime);
8228     }
8229     if (appData.showThinking 
8230         // [HGM] thinking: four options require thinking output to be sent
8231         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8232                                 ) {
8233         SendToProgram("post\n", cps);
8234     }
8235     SendToProgram("hard\n", cps);
8236     if (!appData.ponderNextMove) {
8237         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8238            it without being sure what state we are in first.  "hard"
8239            is not a toggle, so that one is OK.
8240          */
8241         SendToProgram("easy\n", cps);
8242     }
8243     if (cps->usePing) {
8244       sprintf(buf, "ping %d\n", ++cps->lastPing);
8245       SendToProgram(buf, cps);
8246     }
8247     cps->initDone = TRUE;
8248 }   
8249
8250
8251 void
8252 StartChessProgram(cps)
8253      ChessProgramState *cps;
8254 {
8255     char buf[MSG_SIZ];
8256     int err;
8257
8258     if (appData.noChessProgram) return;
8259     cps->initDone = FALSE;
8260
8261     if (strcmp(cps->host, "localhost") == 0) {
8262         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8263     } else if (*appData.remoteShell == NULLCHAR) {
8264         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8265     } else {
8266         if (*appData.remoteUser == NULLCHAR) {
8267           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8268                     cps->program);
8269         } else {
8270           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8271                     cps->host, appData.remoteUser, cps->program);
8272         }
8273         err = StartChildProcess(buf, "", &cps->pr);
8274     }
8275     
8276     if (err != 0) {
8277         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8278         DisplayFatalError(buf, err, 1);
8279         cps->pr = NoProc;
8280         cps->isr = NULL;
8281         return;
8282     }
8283     
8284     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8285     if (cps->protocolVersion > 1) {
8286       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8287       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8288       cps->comboCnt = 0;  //                and values of combo boxes
8289       SendToProgram(buf, cps);
8290     } else {
8291       SendToProgram("xboard\n", cps);
8292     }
8293 }
8294
8295
8296 void
8297 TwoMachinesEventIfReady P((void))
8298 {
8299   if (first.lastPing != first.lastPong) {
8300     DisplayMessage("", _("Waiting for first chess program"));
8301     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8302     return;
8303   }
8304   if (second.lastPing != second.lastPong) {
8305     DisplayMessage("", _("Waiting for second chess program"));
8306     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8307     return;
8308   }
8309   ThawUI();
8310   TwoMachinesEvent();
8311 }
8312
8313 void
8314 NextMatchGame P((void))
8315 {
8316     int index; /* [HGM] autoinc: step load index during match */
8317     Reset(FALSE, TRUE);
8318     if (*appData.loadGameFile != NULLCHAR) {
8319         index = appData.loadGameIndex;
8320         if(index < 0) { // [HGM] autoinc
8321             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8322             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8323         } 
8324         LoadGameFromFile(appData.loadGameFile,
8325                          index,
8326                          appData.loadGameFile, FALSE);
8327     } else if (*appData.loadPositionFile != NULLCHAR) {
8328         index = appData.loadPositionIndex;
8329         if(index < 0) { // [HGM] autoinc
8330             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8331             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8332         } 
8333         LoadPositionFromFile(appData.loadPositionFile,
8334                              index,
8335                              appData.loadPositionFile);
8336     }
8337     TwoMachinesEventIfReady();
8338 }
8339
8340 void UserAdjudicationEvent( int result )
8341 {
8342     ChessMove gameResult = GameIsDrawn;
8343
8344     if( result > 0 ) {
8345         gameResult = WhiteWins;
8346     }
8347     else if( result < 0 ) {
8348         gameResult = BlackWins;
8349     }
8350
8351     if( gameMode == TwoMachinesPlay ) {
8352         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8353     }
8354 }
8355
8356
8357 // [HGM] save: calculate checksum of game to make games easily identifiable
8358 int StringCheckSum(char *s)
8359 {
8360         int i = 0;
8361         if(s==NULL) return 0;
8362         while(*s) i = i*259 + *s++;
8363         return i;
8364 }
8365
8366 int GameCheckSum()
8367 {
8368         int i, sum=0;
8369         for(i=backwardMostMove; i<forwardMostMove; i++) {
8370                 sum += pvInfoList[i].depth;
8371                 sum += StringCheckSum(parseList[i]);
8372                 sum += StringCheckSum(commentList[i]);
8373                 sum *= 261;
8374         }
8375         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8376         return sum + StringCheckSum(commentList[i]);
8377 } // end of save patch
8378
8379 void
8380 GameEnds(result, resultDetails, whosays)
8381      ChessMove result;
8382      char *resultDetails;
8383      int whosays;
8384 {
8385     GameMode nextGameMode;
8386     int isIcsGame;
8387     char buf[MSG_SIZ];
8388
8389     if(endingGame) return; /* [HGM] crash: forbid recursion */
8390     endingGame = 1;
8391
8392     if (appData.debugMode) {
8393       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8394               result, resultDetails ? resultDetails : "(null)", whosays);
8395     }
8396
8397     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8398         /* If we are playing on ICS, the server decides when the
8399            game is over, but the engine can offer to draw, claim 
8400            a draw, or resign. 
8401          */
8402 #if ZIPPY
8403         if (appData.zippyPlay && first.initDone) {
8404             if (result == GameIsDrawn) {
8405                 /* In case draw still needs to be claimed */
8406                 SendToICS(ics_prefix);
8407                 SendToICS("draw\n");
8408             } else if (StrCaseStr(resultDetails, "resign")) {
8409                 SendToICS(ics_prefix);
8410                 SendToICS("resign\n");
8411             }
8412         }
8413 #endif
8414         endingGame = 0; /* [HGM] crash */
8415         return;
8416     }
8417
8418     /* If we're loading the game from a file, stop */
8419     if (whosays == GE_FILE) {
8420       (void) StopLoadGameTimer();
8421       gameFileFP = NULL;
8422     }
8423
8424     /* Cancel draw offers */
8425     first.offeredDraw = second.offeredDraw = 0;
8426
8427     /* If this is an ICS game, only ICS can really say it's done;
8428        if not, anyone can. */
8429     isIcsGame = (gameMode == IcsPlayingWhite || 
8430                  gameMode == IcsPlayingBlack || 
8431                  gameMode == IcsObserving    || 
8432                  gameMode == IcsExamining);
8433
8434     if (!isIcsGame || whosays == GE_ICS) {
8435         /* OK -- not an ICS game, or ICS said it was done */
8436         StopClocks();
8437         if (!isIcsGame && !appData.noChessProgram) 
8438           SetUserThinkingEnables();
8439     
8440         /* [HGM] if a machine claims the game end we verify this claim */
8441         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8442             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8443                 char claimer;
8444                 ChessMove trueResult = (ChessMove) -1;
8445
8446                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8447                                             first.twoMachinesColor[0] :
8448                                             second.twoMachinesColor[0] ;
8449
8450                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8451                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8452                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8453                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8454                 } else
8455                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8456                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8457                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8458                 } else
8459                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8460                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8461                 }
8462
8463                 // now verify win claims, but not in drop games, as we don't understand those yet
8464                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8465                                                  || gameInfo.variant == VariantGreat) &&
8466                     (result == WhiteWins && claimer == 'w' ||
8467                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8468                       if (appData.debugMode) {
8469                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8470                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8471                       }
8472                       if(result != trueResult) {
8473                               sprintf(buf, "False win claim: '%s'", resultDetails);
8474                               result = claimer == 'w' ? BlackWins : WhiteWins;
8475                               resultDetails = buf;
8476                       }
8477                 } else
8478                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8479                     && (forwardMostMove <= backwardMostMove ||
8480                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8481                         (claimer=='b')==(forwardMostMove&1))
8482                                                                                   ) {
8483                       /* [HGM] verify: draws that were not flagged are false claims */
8484                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8485                       result = claimer == 'w' ? BlackWins : WhiteWins;
8486                       resultDetails = buf;
8487                 }
8488                 /* (Claiming a loss is accepted no questions asked!) */
8489             }
8490             /* [HGM] bare: don't allow bare King to win */
8491             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8492                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8493                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8494                && result != GameIsDrawn)
8495             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8496                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8497                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8498                         if(p >= 0 && p <= (int)WhiteKing) k++;
8499                 }
8500                 if (appData.debugMode) {
8501                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8502                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8503                 }
8504                 if(k <= 1) {
8505                         result = GameIsDrawn;
8506                         sprintf(buf, "%s but bare king", resultDetails);
8507                         resultDetails = buf;
8508                 }
8509             }
8510         }
8511
8512
8513         if(serverMoves != NULL && !loadFlag) { char c = '=';
8514             if(result==WhiteWins) c = '+';
8515             if(result==BlackWins) c = '-';
8516             if(resultDetails != NULL)
8517                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8518         }
8519         if (resultDetails != NULL) {
8520             gameInfo.result = result;
8521             gameInfo.resultDetails = StrSave(resultDetails);
8522
8523             /* display last move only if game was not loaded from file */
8524             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8525                 DisplayMove(currentMove - 1);
8526     
8527             if (forwardMostMove != 0) {
8528                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8529                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8530                                                                 ) {
8531                     if (*appData.saveGameFile != NULLCHAR) {
8532                         SaveGameToFile(appData.saveGameFile, TRUE);
8533                     } else if (appData.autoSaveGames) {
8534                         AutoSaveGame();
8535                     }
8536                     if (*appData.savePositionFile != NULLCHAR) {
8537                         SavePositionToFile(appData.savePositionFile);
8538                     }
8539                 }
8540             }
8541
8542             /* Tell program how game ended in case it is learning */
8543             /* [HGM] Moved this to after saving the PGN, just in case */
8544             /* engine died and we got here through time loss. In that */
8545             /* case we will get a fatal error writing the pipe, which */
8546             /* would otherwise lose us the PGN.                       */
8547             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8548             /* output during GameEnds should never be fatal anymore   */
8549             if (gameMode == MachinePlaysWhite ||
8550                 gameMode == MachinePlaysBlack ||
8551                 gameMode == TwoMachinesPlay ||
8552                 gameMode == IcsPlayingWhite ||
8553                 gameMode == IcsPlayingBlack ||
8554                 gameMode == BeginningOfGame) {
8555                 char buf[MSG_SIZ];
8556                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8557                         resultDetails);
8558                 if (first.pr != NoProc) {
8559                     SendToProgram(buf, &first);
8560                 }
8561                 if (second.pr != NoProc &&
8562                     gameMode == TwoMachinesPlay) {
8563                     SendToProgram(buf, &second);
8564                 }
8565             }
8566         }
8567
8568         if (appData.icsActive) {
8569             if (appData.quietPlay &&
8570                 (gameMode == IcsPlayingWhite ||
8571                  gameMode == IcsPlayingBlack)) {
8572                 SendToICS(ics_prefix);
8573                 SendToICS("set shout 1\n");
8574             }
8575             nextGameMode = IcsIdle;
8576             ics_user_moved = FALSE;
8577             /* clean up premove.  It's ugly when the game has ended and the
8578              * premove highlights are still on the board.
8579              */
8580             if (gotPremove) {
8581               gotPremove = FALSE;
8582               ClearPremoveHighlights();
8583               DrawPosition(FALSE, boards[currentMove]);
8584             }
8585             if (whosays == GE_ICS) {
8586                 switch (result) {
8587                 case WhiteWins:
8588                     if (gameMode == IcsPlayingWhite)
8589                         PlayIcsWinSound();
8590                     else if(gameMode == IcsPlayingBlack)
8591                         PlayIcsLossSound();
8592                     break;
8593                 case BlackWins:
8594                     if (gameMode == IcsPlayingBlack)
8595                         PlayIcsWinSound();
8596                     else if(gameMode == IcsPlayingWhite)
8597                         PlayIcsLossSound();
8598                     break;
8599                 case GameIsDrawn:
8600                     PlayIcsDrawSound();
8601                     break;
8602                 default:
8603                     PlayIcsUnfinishedSound();
8604                 }
8605             }
8606         } else if (gameMode == EditGame ||
8607                    gameMode == PlayFromGameFile || 
8608                    gameMode == AnalyzeMode || 
8609                    gameMode == AnalyzeFile) {
8610             nextGameMode = gameMode;
8611         } else {
8612             nextGameMode = EndOfGame;
8613         }
8614         pausing = FALSE;
8615         ModeHighlight();
8616     } else {
8617         nextGameMode = gameMode;
8618     }
8619
8620     if (appData.noChessProgram) {
8621         gameMode = nextGameMode;
8622         ModeHighlight();
8623         endingGame = 0; /* [HGM] crash */
8624         return;
8625     }
8626
8627     if (first.reuse) {
8628         /* Put first chess program into idle state */
8629         if (first.pr != NoProc &&
8630             (gameMode == MachinePlaysWhite ||
8631              gameMode == MachinePlaysBlack ||
8632              gameMode == TwoMachinesPlay ||
8633              gameMode == IcsPlayingWhite ||
8634              gameMode == IcsPlayingBlack ||
8635              gameMode == BeginningOfGame)) {
8636             SendToProgram("force\n", &first);
8637             if (first.usePing) {
8638               char buf[MSG_SIZ];
8639               sprintf(buf, "ping %d\n", ++first.lastPing);
8640               SendToProgram(buf, &first);
8641             }
8642         }
8643     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8644         /* Kill off first chess program */
8645         if (first.isr != NULL)
8646           RemoveInputSource(first.isr);
8647         first.isr = NULL;
8648     
8649         if (first.pr != NoProc) {
8650             ExitAnalyzeMode();
8651             DoSleep( appData.delayBeforeQuit );
8652             SendToProgram("quit\n", &first);
8653             DoSleep( appData.delayAfterQuit );
8654             DestroyChildProcess(first.pr, first.useSigterm);
8655         }
8656         first.pr = NoProc;
8657     }
8658     if (second.reuse) {
8659         /* Put second chess program into idle state */
8660         if (second.pr != NoProc &&
8661             gameMode == TwoMachinesPlay) {
8662             SendToProgram("force\n", &second);
8663             if (second.usePing) {
8664               char buf[MSG_SIZ];
8665               sprintf(buf, "ping %d\n", ++second.lastPing);
8666               SendToProgram(buf, &second);
8667             }
8668         }
8669     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8670         /* Kill off second chess program */
8671         if (second.isr != NULL)
8672           RemoveInputSource(second.isr);
8673         second.isr = NULL;
8674     
8675         if (second.pr != NoProc) {
8676             DoSleep( appData.delayBeforeQuit );
8677             SendToProgram("quit\n", &second);
8678             DoSleep( appData.delayAfterQuit );
8679             DestroyChildProcess(second.pr, second.useSigterm);
8680         }
8681         second.pr = NoProc;
8682     }
8683
8684     if (matchMode && gameMode == TwoMachinesPlay) {
8685         switch (result) {
8686         case WhiteWins:
8687           if (first.twoMachinesColor[0] == 'w') {
8688             first.matchWins++;
8689           } else {
8690             second.matchWins++;
8691           }
8692           break;
8693         case BlackWins:
8694           if (first.twoMachinesColor[0] == 'b') {
8695             first.matchWins++;
8696           } else {
8697             second.matchWins++;
8698           }
8699           break;
8700         default:
8701           break;
8702         }
8703         if (matchGame < appData.matchGames) {
8704             char *tmp;
8705             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8706                 tmp = first.twoMachinesColor;
8707                 first.twoMachinesColor = second.twoMachinesColor;
8708                 second.twoMachinesColor = tmp;
8709             }
8710             gameMode = nextGameMode;
8711             matchGame++;
8712             if(appData.matchPause>10000 || appData.matchPause<10)
8713                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8714             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8715             endingGame = 0; /* [HGM] crash */
8716             return;
8717         } else {
8718             char buf[MSG_SIZ];
8719             gameMode = nextGameMode;
8720             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8721                     first.tidy, second.tidy,
8722                     first.matchWins, second.matchWins,
8723                     appData.matchGames - (first.matchWins + second.matchWins));
8724             DisplayFatalError(buf, 0, 0);
8725         }
8726     }
8727     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8728         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8729       ExitAnalyzeMode();
8730     gameMode = nextGameMode;
8731     ModeHighlight();
8732     endingGame = 0;  /* [HGM] crash */
8733 }
8734
8735 /* Assumes program was just initialized (initString sent).
8736    Leaves program in force mode. */
8737 void
8738 FeedMovesToProgram(cps, upto) 
8739      ChessProgramState *cps;
8740      int upto;
8741 {
8742     int i;
8743     
8744     if (appData.debugMode)
8745       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8746               startedFromSetupPosition ? "position and " : "",
8747               backwardMostMove, upto, cps->which);
8748     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8749         // [HGM] variantswitch: make engine aware of new variant
8750         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8751                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8752         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8753         SendToProgram(buf, cps);
8754         currentlyInitializedVariant = gameInfo.variant;
8755     }
8756     SendToProgram("force\n", cps);
8757     if (startedFromSetupPosition) {
8758         SendBoard(cps, backwardMostMove);
8759     if (appData.debugMode) {
8760         fprintf(debugFP, "feedMoves\n");
8761     }
8762     }
8763     for (i = backwardMostMove; i < upto; i++) {
8764         SendMoveToProgram(i, cps);
8765     }
8766 }
8767
8768
8769 void
8770 ResurrectChessProgram()
8771 {
8772      /* The chess program may have exited.
8773         If so, restart it and feed it all the moves made so far. */
8774
8775     if (appData.noChessProgram || first.pr != NoProc) return;
8776     
8777     StartChessProgram(&first);
8778     InitChessProgram(&first, FALSE);
8779     FeedMovesToProgram(&first, currentMove);
8780
8781     if (!first.sendTime) {
8782         /* can't tell gnuchess what its clock should read,
8783            so we bow to its notion. */
8784         ResetClocks();
8785         timeRemaining[0][currentMove] = whiteTimeRemaining;
8786         timeRemaining[1][currentMove] = blackTimeRemaining;
8787     }
8788
8789     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8790                 appData.icsEngineAnalyze) && first.analysisSupport) {
8791       SendToProgram("analyze\n", &first);
8792       first.analyzing = TRUE;
8793     }
8794 }
8795
8796 /*
8797  * Button procedures
8798  */
8799 void
8800 Reset(redraw, init)
8801      int redraw, init;
8802 {
8803     int i;
8804
8805     if (appData.debugMode) {
8806         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8807                 redraw, init, gameMode);
8808     }
8809     CleanupTail(); // [HGM] vari: delete any stored variations
8810     pausing = pauseExamInvalid = FALSE;
8811     startedFromSetupPosition = blackPlaysFirst = FALSE;
8812     firstMove = TRUE;
8813     whiteFlag = blackFlag = FALSE;
8814     userOfferedDraw = FALSE;
8815     hintRequested = bookRequested = FALSE;
8816     first.maybeThinking = FALSE;
8817     second.maybeThinking = FALSE;
8818     first.bookSuspend = FALSE; // [HGM] book
8819     second.bookSuspend = FALSE;
8820     thinkOutput[0] = NULLCHAR;
8821     lastHint[0] = NULLCHAR;
8822     ClearGameInfo(&gameInfo);
8823     gameInfo.variant = StringToVariant(appData.variant);
8824     ics_user_moved = ics_clock_paused = FALSE;
8825     ics_getting_history = H_FALSE;
8826     ics_gamenum = -1;
8827     white_holding[0] = black_holding[0] = NULLCHAR;
8828     ClearProgramStats();
8829     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8830     
8831     ResetFrontEnd();
8832     ClearHighlights();
8833     flipView = appData.flipView;
8834     ClearPremoveHighlights();
8835     gotPremove = FALSE;
8836     alarmSounded = FALSE;
8837
8838     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8839     if(appData.serverMovesName != NULL) {
8840         /* [HGM] prepare to make moves file for broadcasting */
8841         clock_t t = clock();
8842         if(serverMoves != NULL) fclose(serverMoves);
8843         serverMoves = fopen(appData.serverMovesName, "r");
8844         if(serverMoves != NULL) {
8845             fclose(serverMoves);
8846             /* delay 15 sec before overwriting, so all clients can see end */
8847             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8848         }
8849         serverMoves = fopen(appData.serverMovesName, "w");
8850     }
8851
8852     ExitAnalyzeMode();
8853     gameMode = BeginningOfGame;
8854     ModeHighlight();
8855     if(appData.icsActive) gameInfo.variant = VariantNormal;
8856     currentMove = forwardMostMove = backwardMostMove = 0;
8857     InitPosition(redraw);
8858     for (i = 0; i < MAX_MOVES; i++) {
8859         if (commentList[i] != NULL) {
8860             free(commentList[i]);
8861             commentList[i] = NULL;
8862         }
8863     }
8864     ResetClocks();
8865     timeRemaining[0][0] = whiteTimeRemaining;
8866     timeRemaining[1][0] = blackTimeRemaining;
8867     if (first.pr == NULL) {
8868         StartChessProgram(&first);
8869     }
8870     if (init) {
8871             InitChessProgram(&first, startedFromSetupPosition);
8872     }
8873     DisplayTitle("");
8874     DisplayMessage("", "");
8875     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8876     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8877 }
8878
8879 void
8880 AutoPlayGameLoop()
8881 {
8882     for (;;) {
8883         if (!AutoPlayOneMove())
8884           return;
8885         if (matchMode || appData.timeDelay == 0)
8886           continue;
8887         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8888           return;
8889         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8890         break;
8891     }
8892 }
8893
8894
8895 int
8896 AutoPlayOneMove()
8897 {
8898     int fromX, fromY, toX, toY;
8899
8900     if (appData.debugMode) {
8901       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8902     }
8903
8904     if (gameMode != PlayFromGameFile)
8905       return FALSE;
8906
8907     if (currentMove >= forwardMostMove) {
8908       gameMode = EditGame;
8909       ModeHighlight();
8910
8911       /* [AS] Clear current move marker at the end of a game */
8912       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8913
8914       return FALSE;
8915     }
8916     
8917     toX = moveList[currentMove][2] - AAA;
8918     toY = moveList[currentMove][3] - ONE;
8919
8920     if (moveList[currentMove][1] == '@') {
8921         if (appData.highlightLastMove) {
8922             SetHighlights(-1, -1, toX, toY);
8923         }
8924     } else {
8925         fromX = moveList[currentMove][0] - AAA;
8926         fromY = moveList[currentMove][1] - ONE;
8927
8928         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8929
8930         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8931
8932         if (appData.highlightLastMove) {
8933             SetHighlights(fromX, fromY, toX, toY);
8934         }
8935     }
8936     DisplayMove(currentMove);
8937     SendMoveToProgram(currentMove++, &first);
8938     DisplayBothClocks();
8939     DrawPosition(FALSE, boards[currentMove]);
8940     // [HGM] PV info: always display, routine tests if empty
8941     DisplayComment(currentMove - 1, commentList[currentMove]);
8942     return TRUE;
8943 }
8944
8945
8946 int
8947 LoadGameOneMove(readAhead)
8948      ChessMove readAhead;
8949 {
8950     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8951     char promoChar = NULLCHAR;
8952     ChessMove moveType;
8953     char move[MSG_SIZ];
8954     char *p, *q;
8955     
8956     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8957         gameMode != AnalyzeMode && gameMode != Training) {
8958         gameFileFP = NULL;
8959         return FALSE;
8960     }
8961     
8962     yyboardindex = forwardMostMove;
8963     if (readAhead != (ChessMove)0) {
8964       moveType = readAhead;
8965     } else {
8966       if (gameFileFP == NULL)
8967           return FALSE;
8968       moveType = (ChessMove) yylex();
8969     }
8970     
8971     done = FALSE;
8972     switch (moveType) {
8973       case Comment:
8974         if (appData.debugMode) 
8975           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8976         p = yy_text;
8977
8978         /* append the comment but don't display it */
8979         AppendComment(currentMove, p, FALSE);
8980         return TRUE;
8981
8982       case WhiteCapturesEnPassant:
8983       case BlackCapturesEnPassant:
8984       case WhitePromotionChancellor:
8985       case BlackPromotionChancellor:
8986       case WhitePromotionArchbishop:
8987       case BlackPromotionArchbishop:
8988       case WhitePromotionCentaur:
8989       case BlackPromotionCentaur:
8990       case WhitePromotionQueen:
8991       case BlackPromotionQueen:
8992       case WhitePromotionRook:
8993       case BlackPromotionRook:
8994       case WhitePromotionBishop:
8995       case BlackPromotionBishop:
8996       case WhitePromotionKnight:
8997       case BlackPromotionKnight:
8998       case WhitePromotionKing:
8999       case BlackPromotionKing:
9000       case NormalMove:
9001       case WhiteKingSideCastle:
9002       case WhiteQueenSideCastle:
9003       case BlackKingSideCastle:
9004       case BlackQueenSideCastle:
9005       case WhiteKingSideCastleWild:
9006       case WhiteQueenSideCastleWild:
9007       case BlackKingSideCastleWild:
9008       case BlackQueenSideCastleWild:
9009       /* PUSH Fabien */
9010       case WhiteHSideCastleFR:
9011       case WhiteASideCastleFR:
9012       case BlackHSideCastleFR:
9013       case BlackASideCastleFR:
9014       /* POP Fabien */
9015         if (appData.debugMode)
9016           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9017         fromX = currentMoveString[0] - AAA;
9018         fromY = currentMoveString[1] - ONE;
9019         toX = currentMoveString[2] - AAA;
9020         toY = currentMoveString[3] - ONE;
9021         promoChar = currentMoveString[4];
9022         break;
9023
9024       case WhiteDrop:
9025       case BlackDrop:
9026         if (appData.debugMode)
9027           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9028         fromX = moveType == WhiteDrop ?
9029           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9030         (int) CharToPiece(ToLower(currentMoveString[0]));
9031         fromY = DROP_RANK;
9032         toX = currentMoveString[2] - AAA;
9033         toY = currentMoveString[3] - ONE;
9034         break;
9035
9036       case WhiteWins:
9037       case BlackWins:
9038       case GameIsDrawn:
9039       case GameUnfinished:
9040         if (appData.debugMode)
9041           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9042         p = strchr(yy_text, '{');
9043         if (p == NULL) p = strchr(yy_text, '(');
9044         if (p == NULL) {
9045             p = yy_text;
9046             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9047         } else {
9048             q = strchr(p, *p == '{' ? '}' : ')');
9049             if (q != NULL) *q = NULLCHAR;
9050             p++;
9051         }
9052         GameEnds(moveType, p, GE_FILE);
9053         done = TRUE;
9054         if (cmailMsgLoaded) {
9055             ClearHighlights();
9056             flipView = WhiteOnMove(currentMove);
9057             if (moveType == GameUnfinished) flipView = !flipView;
9058             if (appData.debugMode)
9059               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9060         }
9061         break;
9062
9063       case (ChessMove) 0:       /* end of file */
9064         if (appData.debugMode)
9065           fprintf(debugFP, "Parser hit end of file\n");
9066         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9067           case MT_NONE:
9068           case MT_CHECK:
9069             break;
9070           case MT_CHECKMATE:
9071           case MT_STAINMATE:
9072             if (WhiteOnMove(currentMove)) {
9073                 GameEnds(BlackWins, "Black mates", GE_FILE);
9074             } else {
9075                 GameEnds(WhiteWins, "White mates", GE_FILE);
9076             }
9077             break;
9078           case MT_STALEMATE:
9079             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9080             break;
9081         }
9082         done = TRUE;
9083         break;
9084
9085       case MoveNumberOne:
9086         if (lastLoadGameStart == GNUChessGame) {
9087             /* GNUChessGames have numbers, but they aren't move numbers */
9088             if (appData.debugMode)
9089               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9090                       yy_text, (int) moveType);
9091             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9092         }
9093         /* else fall thru */
9094
9095       case XBoardGame:
9096       case GNUChessGame:
9097       case PGNTag:
9098         /* Reached start of next game in file */
9099         if (appData.debugMode)
9100           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9101         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9102           case MT_NONE:
9103           case MT_CHECK:
9104             break;
9105           case MT_CHECKMATE:
9106           case MT_STAINMATE:
9107             if (WhiteOnMove(currentMove)) {
9108                 GameEnds(BlackWins, "Black mates", GE_FILE);
9109             } else {
9110                 GameEnds(WhiteWins, "White mates", GE_FILE);
9111             }
9112             break;
9113           case MT_STALEMATE:
9114             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9115             break;
9116         }
9117         done = TRUE;
9118         break;
9119
9120       case PositionDiagram:     /* should not happen; ignore */
9121       case ElapsedTime:         /* ignore */
9122       case NAG:                 /* ignore */
9123         if (appData.debugMode)
9124           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9125                   yy_text, (int) moveType);
9126         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9127
9128       case IllegalMove:
9129         if (appData.testLegality) {
9130             if (appData.debugMode)
9131               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9132             sprintf(move, _("Illegal move: %d.%s%s"),
9133                     (forwardMostMove / 2) + 1,
9134                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9135             DisplayError(move, 0);
9136             done = TRUE;
9137         } else {
9138             if (appData.debugMode)
9139               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9140                       yy_text, currentMoveString);
9141             fromX = currentMoveString[0] - AAA;
9142             fromY = currentMoveString[1] - ONE;
9143             toX = currentMoveString[2] - AAA;
9144             toY = currentMoveString[3] - ONE;
9145             promoChar = currentMoveString[4];
9146         }
9147         break;
9148
9149       case AmbiguousMove:
9150         if (appData.debugMode)
9151           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9152         sprintf(move, _("Ambiguous move: %d.%s%s"),
9153                 (forwardMostMove / 2) + 1,
9154                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9155         DisplayError(move, 0);
9156         done = TRUE;
9157         break;
9158
9159       default:
9160       case ImpossibleMove:
9161         if (appData.debugMode)
9162           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9163         sprintf(move, _("Illegal move: %d.%s%s"),
9164                 (forwardMostMove / 2) + 1,
9165                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9166         DisplayError(move, 0);
9167         done = TRUE;
9168         break;
9169     }
9170
9171     if (done) {
9172         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9173             DrawPosition(FALSE, boards[currentMove]);
9174             DisplayBothClocks();
9175             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9176               DisplayComment(currentMove - 1, commentList[currentMove]);
9177         }
9178         (void) StopLoadGameTimer();
9179         gameFileFP = NULL;
9180         cmailOldMove = forwardMostMove;
9181         return FALSE;
9182     } else {
9183         /* currentMoveString is set as a side-effect of yylex */
9184         strcat(currentMoveString, "\n");
9185         strcpy(moveList[forwardMostMove], currentMoveString);
9186         
9187         thinkOutput[0] = NULLCHAR;
9188         MakeMove(fromX, fromY, toX, toY, promoChar);
9189         currentMove = forwardMostMove;
9190         return TRUE;
9191     }
9192 }
9193
9194 /* Load the nth game from the given file */
9195 int
9196 LoadGameFromFile(filename, n, title, useList)
9197      char *filename;
9198      int n;
9199      char *title;
9200      /*Boolean*/ int useList;
9201 {
9202     FILE *f;
9203     char buf[MSG_SIZ];
9204
9205     if (strcmp(filename, "-") == 0) {
9206         f = stdin;
9207         title = "stdin";
9208     } else {
9209         f = fopen(filename, "rb");
9210         if (f == NULL) {
9211           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9212             DisplayError(buf, errno);
9213             return FALSE;
9214         }
9215     }
9216     if (fseek(f, 0, 0) == -1) {
9217         /* f is not seekable; probably a pipe */
9218         useList = FALSE;
9219     }
9220     if (useList && n == 0) {
9221         int error = GameListBuild(f);
9222         if (error) {
9223             DisplayError(_("Cannot build game list"), error);
9224         } else if (!ListEmpty(&gameList) &&
9225                    ((ListGame *) gameList.tailPred)->number > 1) {
9226             GameListPopUp(f, title);
9227             return TRUE;
9228         }
9229         GameListDestroy();
9230         n = 1;
9231     }
9232     if (n == 0) n = 1;
9233     return LoadGame(f, n, title, FALSE);
9234 }
9235
9236
9237 void
9238 MakeRegisteredMove()
9239 {
9240     int fromX, fromY, toX, toY;
9241     char promoChar;
9242     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9243         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9244           case CMAIL_MOVE:
9245           case CMAIL_DRAW:
9246             if (appData.debugMode)
9247               fprintf(debugFP, "Restoring %s for game %d\n",
9248                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9249     
9250             thinkOutput[0] = NULLCHAR;
9251             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9252             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9253             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9254             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9255             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9256             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9257             MakeMove(fromX, fromY, toX, toY, promoChar);
9258             ShowMove(fromX, fromY, toX, toY);
9259               
9260             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9261               case MT_NONE:
9262               case MT_CHECK:
9263                 break;
9264                 
9265               case MT_CHECKMATE:
9266               case MT_STAINMATE:
9267                 if (WhiteOnMove(currentMove)) {
9268                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9269                 } else {
9270                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9271                 }
9272                 break;
9273                 
9274               case MT_STALEMATE:
9275                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9276                 break;
9277             }
9278
9279             break;
9280             
9281           case CMAIL_RESIGN:
9282             if (WhiteOnMove(currentMove)) {
9283                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9284             } else {
9285                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9286             }
9287             break;
9288             
9289           case CMAIL_ACCEPT:
9290             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9291             break;
9292               
9293           default:
9294             break;
9295         }
9296     }
9297
9298     return;
9299 }
9300
9301 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9302 int
9303 CmailLoadGame(f, gameNumber, title, useList)
9304      FILE *f;
9305      int gameNumber;
9306      char *title;
9307      int useList;
9308 {
9309     int retVal;
9310
9311     if (gameNumber > nCmailGames) {
9312         DisplayError(_("No more games in this message"), 0);
9313         return FALSE;
9314     }
9315     if (f == lastLoadGameFP) {
9316         int offset = gameNumber - lastLoadGameNumber;
9317         if (offset == 0) {
9318             cmailMsg[0] = NULLCHAR;
9319             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9320                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9321                 nCmailMovesRegistered--;
9322             }
9323             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9324             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9325                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9326             }
9327         } else {
9328             if (! RegisterMove()) return FALSE;
9329         }
9330     }
9331
9332     retVal = LoadGame(f, gameNumber, title, useList);
9333
9334     /* Make move registered during previous look at this game, if any */
9335     MakeRegisteredMove();
9336
9337     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9338         commentList[currentMove]
9339           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9340         DisplayComment(currentMove - 1, commentList[currentMove]);
9341     }
9342
9343     return retVal;
9344 }
9345
9346 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9347 int
9348 ReloadGame(offset)
9349      int offset;
9350 {
9351     int gameNumber = lastLoadGameNumber + offset;
9352     if (lastLoadGameFP == NULL) {
9353         DisplayError(_("No game has been loaded yet"), 0);
9354         return FALSE;
9355     }
9356     if (gameNumber <= 0) {
9357         DisplayError(_("Can't back up any further"), 0);
9358         return FALSE;
9359     }
9360     if (cmailMsgLoaded) {
9361         return CmailLoadGame(lastLoadGameFP, gameNumber,
9362                              lastLoadGameTitle, lastLoadGameUseList);
9363     } else {
9364         return LoadGame(lastLoadGameFP, gameNumber,
9365                         lastLoadGameTitle, lastLoadGameUseList);
9366     }
9367 }
9368
9369
9370
9371 /* Load the nth game from open file f */
9372 int
9373 LoadGame(f, gameNumber, title, useList)
9374      FILE *f;
9375      int gameNumber;
9376      char *title;
9377      int useList;
9378 {
9379     ChessMove cm;
9380     char buf[MSG_SIZ];
9381     int gn = gameNumber;
9382     ListGame *lg = NULL;
9383     int numPGNTags = 0;
9384     int err;
9385     GameMode oldGameMode;
9386     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9387
9388     if (appData.debugMode) 
9389         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9390
9391     if (gameMode == Training )
9392         SetTrainingModeOff();
9393
9394     oldGameMode = gameMode;
9395     if (gameMode != BeginningOfGame) {
9396       Reset(FALSE, TRUE);
9397     }
9398
9399     gameFileFP = f;
9400     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9401         fclose(lastLoadGameFP);
9402     }
9403
9404     if (useList) {
9405         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9406         
9407         if (lg) {
9408             fseek(f, lg->offset, 0);
9409             GameListHighlight(gameNumber);
9410             gn = 1;
9411         }
9412         else {
9413             DisplayError(_("Game number out of range"), 0);
9414             return FALSE;
9415         }
9416     } else {
9417         GameListDestroy();
9418         if (fseek(f, 0, 0) == -1) {
9419             if (f == lastLoadGameFP ?
9420                 gameNumber == lastLoadGameNumber + 1 :
9421                 gameNumber == 1) {
9422                 gn = 1;
9423             } else {
9424                 DisplayError(_("Can't seek on game file"), 0);
9425                 return FALSE;
9426             }
9427         }
9428     }
9429     lastLoadGameFP = f;
9430     lastLoadGameNumber = gameNumber;
9431     strcpy(lastLoadGameTitle, title);
9432     lastLoadGameUseList = useList;
9433
9434     yynewfile(f);
9435
9436     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9437       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9438                 lg->gameInfo.black);
9439             DisplayTitle(buf);
9440     } else if (*title != NULLCHAR) {
9441         if (gameNumber > 1) {
9442             sprintf(buf, "%s %d", title, gameNumber);
9443             DisplayTitle(buf);
9444         } else {
9445             DisplayTitle(title);
9446         }
9447     }
9448
9449     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9450         gameMode = PlayFromGameFile;
9451         ModeHighlight();
9452     }
9453
9454     currentMove = forwardMostMove = backwardMostMove = 0;
9455     CopyBoard(boards[0], initialPosition);
9456     StopClocks();
9457
9458     /*
9459      * Skip the first gn-1 games in the file.
9460      * Also skip over anything that precedes an identifiable 
9461      * start of game marker, to avoid being confused by 
9462      * garbage at the start of the file.  Currently 
9463      * recognized start of game markers are the move number "1",
9464      * the pattern "gnuchess .* game", the pattern
9465      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9466      * A game that starts with one of the latter two patterns
9467      * will also have a move number 1, possibly
9468      * following a position diagram.
9469      * 5-4-02: Let's try being more lenient and allowing a game to
9470      * start with an unnumbered move.  Does that break anything?
9471      */
9472     cm = lastLoadGameStart = (ChessMove) 0;
9473     while (gn > 0) {
9474         yyboardindex = forwardMostMove;
9475         cm = (ChessMove) yylex();
9476         switch (cm) {
9477           case (ChessMove) 0:
9478             if (cmailMsgLoaded) {
9479                 nCmailGames = CMAIL_MAX_GAMES - gn;
9480             } else {
9481                 Reset(TRUE, TRUE);
9482                 DisplayError(_("Game not found in file"), 0);
9483             }
9484             return FALSE;
9485
9486           case GNUChessGame:
9487           case XBoardGame:
9488             gn--;
9489             lastLoadGameStart = cm;
9490             break;
9491             
9492           case MoveNumberOne:
9493             switch (lastLoadGameStart) {
9494               case GNUChessGame:
9495               case XBoardGame:
9496               case PGNTag:
9497                 break;
9498               case MoveNumberOne:
9499               case (ChessMove) 0:
9500                 gn--;           /* count this game */
9501                 lastLoadGameStart = cm;
9502                 break;
9503               default:
9504                 /* impossible */
9505                 break;
9506             }
9507             break;
9508
9509           case PGNTag:
9510             switch (lastLoadGameStart) {
9511               case GNUChessGame:
9512               case PGNTag:
9513               case MoveNumberOne:
9514               case (ChessMove) 0:
9515                 gn--;           /* count this game */
9516                 lastLoadGameStart = cm;
9517                 break;
9518               case XBoardGame:
9519                 lastLoadGameStart = cm; /* game counted already */
9520                 break;
9521               default:
9522                 /* impossible */
9523                 break;
9524             }
9525             if (gn > 0) {
9526                 do {
9527                     yyboardindex = forwardMostMove;
9528                     cm = (ChessMove) yylex();
9529                 } while (cm == PGNTag || cm == Comment);
9530             }
9531             break;
9532
9533           case WhiteWins:
9534           case BlackWins:
9535           case GameIsDrawn:
9536             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9537                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9538                     != CMAIL_OLD_RESULT) {
9539                     nCmailResults ++ ;
9540                     cmailResult[  CMAIL_MAX_GAMES
9541                                 - gn - 1] = CMAIL_OLD_RESULT;
9542                 }
9543             }
9544             break;
9545
9546           case NormalMove:
9547             /* Only a NormalMove can be at the start of a game
9548              * without a position diagram. */
9549             if (lastLoadGameStart == (ChessMove) 0) {
9550               gn--;
9551               lastLoadGameStart = MoveNumberOne;
9552             }
9553             break;
9554
9555           default:
9556             break;
9557         }
9558     }
9559     
9560     if (appData.debugMode)
9561       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9562
9563     if (cm == XBoardGame) {
9564         /* Skip any header junk before position diagram and/or move 1 */
9565         for (;;) {
9566             yyboardindex = forwardMostMove;
9567             cm = (ChessMove) yylex();
9568
9569             if (cm == (ChessMove) 0 ||
9570                 cm == GNUChessGame || cm == XBoardGame) {
9571                 /* Empty game; pretend end-of-file and handle later */
9572                 cm = (ChessMove) 0;
9573                 break;
9574             }
9575
9576             if (cm == MoveNumberOne || cm == PositionDiagram ||
9577                 cm == PGNTag || cm == Comment)
9578               break;
9579         }
9580     } else if (cm == GNUChessGame) {
9581         if (gameInfo.event != NULL) {
9582             free(gameInfo.event);
9583         }
9584         gameInfo.event = StrSave(yy_text);
9585     }   
9586
9587     startedFromSetupPosition = FALSE;
9588     while (cm == PGNTag) {
9589         if (appData.debugMode) 
9590           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9591         err = ParsePGNTag(yy_text, &gameInfo);
9592         if (!err) numPGNTags++;
9593
9594         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9595         if(gameInfo.variant != oldVariant) {
9596             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9597             InitPosition(TRUE);
9598             oldVariant = gameInfo.variant;
9599             if (appData.debugMode) 
9600               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9601         }
9602
9603
9604         if (gameInfo.fen != NULL) {
9605           Board initial_position;
9606           startedFromSetupPosition = TRUE;
9607           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9608             Reset(TRUE, TRUE);
9609             DisplayError(_("Bad FEN position in file"), 0);
9610             return FALSE;
9611           }
9612           CopyBoard(boards[0], initial_position);
9613           if (blackPlaysFirst) {
9614             currentMove = forwardMostMove = backwardMostMove = 1;
9615             CopyBoard(boards[1], initial_position);
9616             strcpy(moveList[0], "");
9617             strcpy(parseList[0], "");
9618             timeRemaining[0][1] = whiteTimeRemaining;
9619             timeRemaining[1][1] = blackTimeRemaining;
9620             if (commentList[0] != NULL) {
9621               commentList[1] = commentList[0];
9622               commentList[0] = NULL;
9623             }
9624           } else {
9625             currentMove = forwardMostMove = backwardMostMove = 0;
9626           }
9627           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9628           {   int i;
9629               initialRulePlies = FENrulePlies;
9630               for( i=0; i< nrCastlingRights; i++ )
9631                   initialRights[i] = initial_position[CASTLING][i];
9632           }
9633           yyboardindex = forwardMostMove;
9634           free(gameInfo.fen);
9635           gameInfo.fen = NULL;
9636         }
9637
9638         yyboardindex = forwardMostMove;
9639         cm = (ChessMove) yylex();
9640
9641         /* Handle comments interspersed among the tags */
9642         while (cm == Comment) {
9643             char *p;
9644             if (appData.debugMode) 
9645               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9646             p = yy_text;
9647             AppendComment(currentMove, p, FALSE);
9648             yyboardindex = forwardMostMove;
9649             cm = (ChessMove) yylex();
9650         }
9651     }
9652
9653     /* don't rely on existence of Event tag since if game was
9654      * pasted from clipboard the Event tag may not exist
9655      */
9656     if (numPGNTags > 0){
9657         char *tags;
9658         if (gameInfo.variant == VariantNormal) {
9659           gameInfo.variant = StringToVariant(gameInfo.event);
9660         }
9661         if (!matchMode) {
9662           if( appData.autoDisplayTags ) {
9663             tags = PGNTags(&gameInfo);
9664             TagsPopUp(tags, CmailMsg());
9665             free(tags);
9666           }
9667         }
9668     } else {
9669         /* Make something up, but don't display it now */
9670         SetGameInfo();
9671         TagsPopDown();
9672     }
9673
9674     if (cm == PositionDiagram) {
9675         int i, j;
9676         char *p;
9677         Board initial_position;
9678
9679         if (appData.debugMode)
9680           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9681
9682         if (!startedFromSetupPosition) {
9683             p = yy_text;
9684             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9685               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9686                 switch (*p) {
9687                   case '[':
9688                   case '-':
9689                   case ' ':
9690                   case '\t':
9691                   case '\n':
9692                   case '\r':
9693                     break;
9694                   default:
9695                     initial_position[i][j++] = CharToPiece(*p);
9696                     break;
9697                 }
9698             while (*p == ' ' || *p == '\t' ||
9699                    *p == '\n' || *p == '\r') p++;
9700         
9701             if (strncmp(p, "black", strlen("black"))==0)
9702               blackPlaysFirst = TRUE;
9703             else
9704               blackPlaysFirst = FALSE;
9705             startedFromSetupPosition = TRUE;
9706         
9707             CopyBoard(boards[0], initial_position);
9708             if (blackPlaysFirst) {
9709                 currentMove = forwardMostMove = backwardMostMove = 1;
9710                 CopyBoard(boards[1], initial_position);
9711                 strcpy(moveList[0], "");
9712                 strcpy(parseList[0], "");
9713                 timeRemaining[0][1] = whiteTimeRemaining;
9714                 timeRemaining[1][1] = blackTimeRemaining;
9715                 if (commentList[0] != NULL) {
9716                     commentList[1] = commentList[0];
9717                     commentList[0] = NULL;
9718                 }
9719             } else {
9720                 currentMove = forwardMostMove = backwardMostMove = 0;
9721             }
9722         }
9723         yyboardindex = forwardMostMove;
9724         cm = (ChessMove) yylex();
9725     }
9726
9727     if (first.pr == NoProc) {
9728         StartChessProgram(&first);
9729     }
9730     InitChessProgram(&first, FALSE);
9731     SendToProgram("force\n", &first);
9732     if (startedFromSetupPosition) {
9733         SendBoard(&first, forwardMostMove);
9734     if (appData.debugMode) {
9735         fprintf(debugFP, "Load Game\n");
9736     }
9737         DisplayBothClocks();
9738     }      
9739
9740     /* [HGM] server: flag to write setup moves in broadcast file as one */
9741     loadFlag = appData.suppressLoadMoves;
9742
9743     while (cm == Comment) {
9744         char *p;
9745         if (appData.debugMode) 
9746           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9747         p = yy_text;
9748         AppendComment(currentMove, p, FALSE);
9749         yyboardindex = forwardMostMove;
9750         cm = (ChessMove) yylex();
9751     }
9752
9753     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9754         cm == WhiteWins || cm == BlackWins ||
9755         cm == GameIsDrawn || cm == GameUnfinished) {
9756         DisplayMessage("", _("No moves in game"));
9757         if (cmailMsgLoaded) {
9758             if (appData.debugMode)
9759               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9760             ClearHighlights();
9761             flipView = FALSE;
9762         }
9763         DrawPosition(FALSE, boards[currentMove]);
9764         DisplayBothClocks();
9765         gameMode = EditGame;
9766         ModeHighlight();
9767         gameFileFP = NULL;
9768         cmailOldMove = 0;
9769         return TRUE;
9770     }
9771
9772     // [HGM] PV info: routine tests if comment empty
9773     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9774         DisplayComment(currentMove - 1, commentList[currentMove]);
9775     }
9776     if (!matchMode && appData.timeDelay != 0) 
9777       DrawPosition(FALSE, boards[currentMove]);
9778
9779     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9780       programStats.ok_to_send = 1;
9781     }
9782
9783     /* if the first token after the PGN tags is a move
9784      * and not move number 1, retrieve it from the parser 
9785      */
9786     if (cm != MoveNumberOne)
9787         LoadGameOneMove(cm);
9788
9789     /* load the remaining moves from the file */
9790     while (LoadGameOneMove((ChessMove)0)) {
9791       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9792       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9793     }
9794
9795     /* rewind to the start of the game */
9796     currentMove = backwardMostMove;
9797
9798     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9799
9800     if (oldGameMode == AnalyzeFile ||
9801         oldGameMode == AnalyzeMode) {
9802       AnalyzeFileEvent();
9803     }
9804
9805     if (matchMode || appData.timeDelay == 0) {
9806       ToEndEvent();
9807       gameMode = EditGame;
9808       ModeHighlight();
9809     } else if (appData.timeDelay > 0) {
9810       AutoPlayGameLoop();
9811     }
9812
9813     if (appData.debugMode) 
9814         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9815
9816     loadFlag = 0; /* [HGM] true game starts */
9817     return TRUE;
9818 }
9819
9820 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9821 int
9822 ReloadPosition(offset)
9823      int offset;
9824 {
9825     int positionNumber = lastLoadPositionNumber + offset;
9826     if (lastLoadPositionFP == NULL) {
9827         DisplayError(_("No position has been loaded yet"), 0);
9828         return FALSE;
9829     }
9830     if (positionNumber <= 0) {
9831         DisplayError(_("Can't back up any further"), 0);
9832         return FALSE;
9833     }
9834     return LoadPosition(lastLoadPositionFP, positionNumber,
9835                         lastLoadPositionTitle);
9836 }
9837
9838 /* Load the nth position from the given file */
9839 int
9840 LoadPositionFromFile(filename, n, title)
9841      char *filename;
9842      int n;
9843      char *title;
9844 {
9845     FILE *f;
9846     char buf[MSG_SIZ];
9847
9848     if (strcmp(filename, "-") == 0) {
9849         return LoadPosition(stdin, n, "stdin");
9850     } else {
9851         f = fopen(filename, "rb");
9852         if (f == NULL) {
9853             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9854             DisplayError(buf, errno);
9855             return FALSE;
9856         } else {
9857             return LoadPosition(f, n, title);
9858         }
9859     }
9860 }
9861
9862 /* Load the nth position from the given open file, and close it */
9863 int
9864 LoadPosition(f, positionNumber, title)
9865      FILE *f;
9866      int positionNumber;
9867      char *title;
9868 {
9869     char *p, line[MSG_SIZ];
9870     Board initial_position;
9871     int i, j, fenMode, pn;
9872     
9873     if (gameMode == Training )
9874         SetTrainingModeOff();
9875
9876     if (gameMode != BeginningOfGame) {
9877         Reset(FALSE, TRUE);
9878     }
9879     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9880         fclose(lastLoadPositionFP);
9881     }
9882     if (positionNumber == 0) positionNumber = 1;
9883     lastLoadPositionFP = f;
9884     lastLoadPositionNumber = positionNumber;
9885     strcpy(lastLoadPositionTitle, title);
9886     if (first.pr == NoProc) {
9887       StartChessProgram(&first);
9888       InitChessProgram(&first, FALSE);
9889     }    
9890     pn = positionNumber;
9891     if (positionNumber < 0) {
9892         /* Negative position number means to seek to that byte offset */
9893         if (fseek(f, -positionNumber, 0) == -1) {
9894             DisplayError(_("Can't seek on position file"), 0);
9895             return FALSE;
9896         };
9897         pn = 1;
9898     } else {
9899         if (fseek(f, 0, 0) == -1) {
9900             if (f == lastLoadPositionFP ?
9901                 positionNumber == lastLoadPositionNumber + 1 :
9902                 positionNumber == 1) {
9903                 pn = 1;
9904             } else {
9905                 DisplayError(_("Can't seek on position file"), 0);
9906                 return FALSE;
9907             }
9908         }
9909     }
9910     /* See if this file is FEN or old-style xboard */
9911     if (fgets(line, MSG_SIZ, f) == NULL) {
9912         DisplayError(_("Position not found in file"), 0);
9913         return FALSE;
9914     }
9915     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9916     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9917
9918     if (pn >= 2) {
9919         if (fenMode || line[0] == '#') pn--;
9920         while (pn > 0) {
9921             /* skip positions before number pn */
9922             if (fgets(line, MSG_SIZ, f) == NULL) {
9923                 Reset(TRUE, TRUE);
9924                 DisplayError(_("Position not found in file"), 0);
9925                 return FALSE;
9926             }
9927             if (fenMode || line[0] == '#') pn--;
9928         }
9929     }
9930
9931     if (fenMode) {
9932         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9933             DisplayError(_("Bad FEN position in file"), 0);
9934             return FALSE;
9935         }
9936     } else {
9937         (void) fgets(line, MSG_SIZ, f);
9938         (void) fgets(line, MSG_SIZ, f);
9939     
9940         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9941             (void) fgets(line, MSG_SIZ, f);
9942             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9943                 if (*p == ' ')
9944                   continue;
9945                 initial_position[i][j++] = CharToPiece(*p);
9946             }
9947         }
9948     
9949         blackPlaysFirst = FALSE;
9950         if (!feof(f)) {
9951             (void) fgets(line, MSG_SIZ, f);
9952             if (strncmp(line, "black", strlen("black"))==0)
9953               blackPlaysFirst = TRUE;
9954         }
9955     }
9956     startedFromSetupPosition = TRUE;
9957     
9958     SendToProgram("force\n", &first);
9959     CopyBoard(boards[0], initial_position);
9960     if (blackPlaysFirst) {
9961         currentMove = forwardMostMove = backwardMostMove = 1;
9962         strcpy(moveList[0], "");
9963         strcpy(parseList[0], "");
9964         CopyBoard(boards[1], initial_position);
9965         DisplayMessage("", _("Black to play"));
9966     } else {
9967         currentMove = forwardMostMove = backwardMostMove = 0;
9968         DisplayMessage("", _("White to play"));
9969     }
9970     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9971     SendBoard(&first, forwardMostMove);
9972     if (appData.debugMode) {
9973 int i, j;
9974   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9975   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9976         fprintf(debugFP, "Load Position\n");
9977     }
9978
9979     if (positionNumber > 1) {
9980         sprintf(line, "%s %d", title, positionNumber);
9981         DisplayTitle(line);
9982     } else {
9983         DisplayTitle(title);
9984     }
9985     gameMode = EditGame;
9986     ModeHighlight();
9987     ResetClocks();
9988     timeRemaining[0][1] = whiteTimeRemaining;
9989     timeRemaining[1][1] = blackTimeRemaining;
9990     DrawPosition(FALSE, boards[currentMove]);
9991    
9992     return TRUE;
9993 }
9994
9995
9996 void
9997 CopyPlayerNameIntoFileName(dest, src)
9998      char **dest, *src;
9999 {
10000     while (*src != NULLCHAR && *src != ',') {
10001         if (*src == ' ') {
10002             *(*dest)++ = '_';
10003             src++;
10004         } else {
10005             *(*dest)++ = *src++;
10006         }
10007     }
10008 }
10009
10010 char *DefaultFileName(ext)
10011      char *ext;
10012 {
10013     static char def[MSG_SIZ];
10014     char *p;
10015
10016     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10017         p = def;
10018         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10019         *p++ = '-';
10020         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10021         *p++ = '.';
10022         strcpy(p, ext);
10023     } else {
10024         def[0] = NULLCHAR;
10025     }
10026     return def;
10027 }
10028
10029 /* Save the current game to the given file */
10030 int
10031 SaveGameToFile(filename, append)
10032      char *filename;
10033      int append;
10034 {
10035     FILE *f;
10036     char buf[MSG_SIZ];
10037
10038     if (strcmp(filename, "-") == 0) {
10039         return SaveGame(stdout, 0, NULL);
10040     } else {
10041         f = fopen(filename, append ? "a" : "w");
10042         if (f == NULL) {
10043             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10044             DisplayError(buf, errno);
10045             return FALSE;
10046         } else {
10047             return SaveGame(f, 0, NULL);
10048         }
10049     }
10050 }
10051
10052 char *
10053 SavePart(str)
10054      char *str;
10055 {
10056     static char buf[MSG_SIZ];
10057     char *p;
10058     
10059     p = strchr(str, ' ');
10060     if (p == NULL) return str;
10061     strncpy(buf, str, p - str);
10062     buf[p - str] = NULLCHAR;
10063     return buf;
10064 }
10065
10066 #define PGN_MAX_LINE 75
10067
10068 #define PGN_SIDE_WHITE  0
10069 #define PGN_SIDE_BLACK  1
10070
10071 /* [AS] */
10072 static int FindFirstMoveOutOfBook( int side )
10073 {
10074     int result = -1;
10075
10076     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10077         int index = backwardMostMove;
10078         int has_book_hit = 0;
10079
10080         if( (index % 2) != side ) {
10081             index++;
10082         }
10083
10084         while( index < forwardMostMove ) {
10085             /* Check to see if engine is in book */
10086             int depth = pvInfoList[index].depth;
10087             int score = pvInfoList[index].score;
10088             int in_book = 0;
10089
10090             if( depth <= 2 ) {
10091                 in_book = 1;
10092             }
10093             else if( score == 0 && depth == 63 ) {
10094                 in_book = 1; /* Zappa */
10095             }
10096             else if( score == 2 && depth == 99 ) {
10097                 in_book = 1; /* Abrok */
10098             }
10099
10100             has_book_hit += in_book;
10101
10102             if( ! in_book ) {
10103                 result = index;
10104
10105                 break;
10106             }
10107
10108             index += 2;
10109         }
10110     }
10111
10112     return result;
10113 }
10114
10115 /* [AS] */
10116 void GetOutOfBookInfo( char * buf )
10117 {
10118     int oob[2];
10119     int i;
10120     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10121
10122     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10123     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10124
10125     *buf = '\0';
10126
10127     if( oob[0] >= 0 || oob[1] >= 0 ) {
10128         for( i=0; i<2; i++ ) {
10129             int idx = oob[i];
10130
10131             if( idx >= 0 ) {
10132                 if( i > 0 && oob[0] >= 0 ) {
10133                     strcat( buf, "   " );
10134                 }
10135
10136                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10137                 sprintf( buf+strlen(buf), "%s%.2f", 
10138                     pvInfoList[idx].score >= 0 ? "+" : "",
10139                     pvInfoList[idx].score / 100.0 );
10140             }
10141         }
10142     }
10143 }
10144
10145 /* Save game in PGN style and close the file */
10146 int
10147 SaveGamePGN(f)
10148      FILE *f;
10149 {
10150     int i, offset, linelen, newblock;
10151     time_t tm;
10152 //    char *movetext;
10153     char numtext[32];
10154     int movelen, numlen, blank;
10155     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10156
10157     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10158     
10159     tm = time((time_t *) NULL);
10160     
10161     PrintPGNTags(f, &gameInfo);
10162     
10163     if (backwardMostMove > 0 || startedFromSetupPosition) {
10164         char *fen = PositionToFEN(backwardMostMove, NULL);
10165         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10166         fprintf(f, "\n{--------------\n");
10167         PrintPosition(f, backwardMostMove);
10168         fprintf(f, "--------------}\n");
10169         free(fen);
10170     }
10171     else {
10172         /* [AS] Out of book annotation */
10173         if( appData.saveOutOfBookInfo ) {
10174             char buf[64];
10175
10176             GetOutOfBookInfo( buf );
10177
10178             if( buf[0] != '\0' ) {
10179                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10180             }
10181         }
10182
10183         fprintf(f, "\n");
10184     }
10185
10186     i = backwardMostMove;
10187     linelen = 0;
10188     newblock = TRUE;
10189
10190     while (i < forwardMostMove) {
10191         /* Print comments preceding this move */
10192         if (commentList[i] != NULL) {
10193             if (linelen > 0) fprintf(f, "\n");
10194             fprintf(f, "%s", commentList[i]);
10195             linelen = 0;
10196             newblock = TRUE;
10197         }
10198
10199         /* Format move number */
10200         if ((i % 2) == 0) {
10201             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10202         } else {
10203             if (newblock) {
10204                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10205             } else {
10206                 numtext[0] = NULLCHAR;
10207             }
10208         }
10209         numlen = strlen(numtext);
10210         newblock = FALSE;
10211
10212         /* Print move number */
10213         blank = linelen > 0 && numlen > 0;
10214         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10215             fprintf(f, "\n");
10216             linelen = 0;
10217             blank = 0;
10218         }
10219         if (blank) {
10220             fprintf(f, " ");
10221             linelen++;
10222         }
10223         fprintf(f, "%s", numtext);
10224         linelen += numlen;
10225
10226         /* Get move */
10227         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10228         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10229
10230         /* Print move */
10231         blank = linelen > 0 && movelen > 0;
10232         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10233             fprintf(f, "\n");
10234             linelen = 0;
10235             blank = 0;
10236         }
10237         if (blank) {
10238             fprintf(f, " ");
10239             linelen++;
10240         }
10241         fprintf(f, "%s", move_buffer);
10242         linelen += movelen;
10243
10244         /* [AS] Add PV info if present */
10245         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10246             /* [HGM] add time */
10247             char buf[MSG_SIZ]; int seconds;
10248
10249             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10250
10251             if( seconds <= 0) buf[0] = 0; else
10252             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10253                 seconds = (seconds + 4)/10; // round to full seconds
10254                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10255                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10256             }
10257
10258             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10259                 pvInfoList[i].score >= 0 ? "+" : "",
10260                 pvInfoList[i].score / 100.0,
10261                 pvInfoList[i].depth,
10262                 buf );
10263
10264             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10265
10266             /* Print score/depth */
10267             blank = linelen > 0 && movelen > 0;
10268             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10269                 fprintf(f, "\n");
10270                 linelen = 0;
10271                 blank = 0;
10272             }
10273             if (blank) {
10274                 fprintf(f, " ");
10275                 linelen++;
10276             }
10277             fprintf(f, "%s", move_buffer);
10278             linelen += movelen;
10279         }
10280
10281         i++;
10282     }
10283     
10284     /* Start a new line */
10285     if (linelen > 0) fprintf(f, "\n");
10286
10287     /* Print comments after last move */
10288     if (commentList[i] != NULL) {
10289         fprintf(f, "%s\n", commentList[i]);
10290     }
10291
10292     /* Print result */
10293     if (gameInfo.resultDetails != NULL &&
10294         gameInfo.resultDetails[0] != NULLCHAR) {
10295         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10296                 PGNResult(gameInfo.result));
10297     } else {
10298         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10299     }
10300
10301     fclose(f);
10302     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10303     return TRUE;
10304 }
10305
10306 /* Save game in old style and close the file */
10307 int
10308 SaveGameOldStyle(f)
10309      FILE *f;
10310 {
10311     int i, offset;
10312     time_t tm;
10313     
10314     tm = time((time_t *) NULL);
10315     
10316     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10317     PrintOpponents(f);
10318     
10319     if (backwardMostMove > 0 || startedFromSetupPosition) {
10320         fprintf(f, "\n[--------------\n");
10321         PrintPosition(f, backwardMostMove);
10322         fprintf(f, "--------------]\n");
10323     } else {
10324         fprintf(f, "\n");
10325     }
10326
10327     i = backwardMostMove;
10328     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10329
10330     while (i < forwardMostMove) {
10331         if (commentList[i] != NULL) {
10332             fprintf(f, "[%s]\n", commentList[i]);
10333         }
10334
10335         if ((i % 2) == 1) {
10336             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10337             i++;
10338         } else {
10339             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10340             i++;
10341             if (commentList[i] != NULL) {
10342                 fprintf(f, "\n");
10343                 continue;
10344             }
10345             if (i >= forwardMostMove) {
10346                 fprintf(f, "\n");
10347                 break;
10348             }
10349             fprintf(f, "%s\n", parseList[i]);
10350             i++;
10351         }
10352     }
10353     
10354     if (commentList[i] != NULL) {
10355         fprintf(f, "[%s]\n", commentList[i]);
10356     }
10357
10358     /* This isn't really the old style, but it's close enough */
10359     if (gameInfo.resultDetails != NULL &&
10360         gameInfo.resultDetails[0] != NULLCHAR) {
10361         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10362                 gameInfo.resultDetails);
10363     } else {
10364         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10365     }
10366
10367     fclose(f);
10368     return TRUE;
10369 }
10370
10371 /* Save the current game to open file f and close the file */
10372 int
10373 SaveGame(f, dummy, dummy2)
10374      FILE *f;
10375      int dummy;
10376      char *dummy2;
10377 {
10378     if (gameMode == EditPosition) EditPositionDone(TRUE);
10379     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10380     if (appData.oldSaveStyle)
10381       return SaveGameOldStyle(f);
10382     else
10383       return SaveGamePGN(f);
10384 }
10385
10386 /* Save the current position to the given file */
10387 int
10388 SavePositionToFile(filename)
10389      char *filename;
10390 {
10391     FILE *f;
10392     char buf[MSG_SIZ];
10393
10394     if (strcmp(filename, "-") == 0) {
10395         return SavePosition(stdout, 0, NULL);
10396     } else {
10397         f = fopen(filename, "a");
10398         if (f == NULL) {
10399             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10400             DisplayError(buf, errno);
10401             return FALSE;
10402         } else {
10403             SavePosition(f, 0, NULL);
10404             return TRUE;
10405         }
10406     }
10407 }
10408
10409 /* Save the current position to the given open file and close the file */
10410 int
10411 SavePosition(f, dummy, dummy2)
10412      FILE *f;
10413      int dummy;
10414      char *dummy2;
10415 {
10416     time_t tm;
10417     char *fen;
10418     
10419     if (gameMode == EditPosition) EditPositionDone(TRUE);
10420     if (appData.oldSaveStyle) {
10421         tm = time((time_t *) NULL);
10422     
10423         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10424         PrintOpponents(f);
10425         fprintf(f, "[--------------\n");
10426         PrintPosition(f, currentMove);
10427         fprintf(f, "--------------]\n");
10428     } else {
10429         fen = PositionToFEN(currentMove, NULL);
10430         fprintf(f, "%s\n", fen);
10431         free(fen);
10432     }
10433     fclose(f);
10434     return TRUE;
10435 }
10436
10437 void
10438 ReloadCmailMsgEvent(unregister)
10439      int unregister;
10440 {
10441 #if !WIN32
10442     static char *inFilename = NULL;
10443     static char *outFilename;
10444     int i;
10445     struct stat inbuf, outbuf;
10446     int status;
10447     
10448     /* Any registered moves are unregistered if unregister is set, */
10449     /* i.e. invoked by the signal handler */
10450     if (unregister) {
10451         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10452             cmailMoveRegistered[i] = FALSE;
10453             if (cmailCommentList[i] != NULL) {
10454                 free(cmailCommentList[i]);
10455                 cmailCommentList[i] = NULL;
10456             }
10457         }
10458         nCmailMovesRegistered = 0;
10459     }
10460
10461     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10462         cmailResult[i] = CMAIL_NOT_RESULT;
10463     }
10464     nCmailResults = 0;
10465
10466     if (inFilename == NULL) {
10467         /* Because the filenames are static they only get malloced once  */
10468         /* and they never get freed                                      */
10469         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10470         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10471
10472         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10473         sprintf(outFilename, "%s.out", appData.cmailGameName);
10474     }
10475     
10476     status = stat(outFilename, &outbuf);
10477     if (status < 0) {
10478         cmailMailedMove = FALSE;
10479     } else {
10480         status = stat(inFilename, &inbuf);
10481         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10482     }
10483     
10484     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10485        counts the games, notes how each one terminated, etc.
10486        
10487        It would be nice to remove this kludge and instead gather all
10488        the information while building the game list.  (And to keep it
10489        in the game list nodes instead of having a bunch of fixed-size
10490        parallel arrays.)  Note this will require getting each game's
10491        termination from the PGN tags, as the game list builder does
10492        not process the game moves.  --mann
10493        */
10494     cmailMsgLoaded = TRUE;
10495     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10496     
10497     /* Load first game in the file or popup game menu */
10498     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10499
10500 #endif /* !WIN32 */
10501     return;
10502 }
10503
10504 int
10505 RegisterMove()
10506 {
10507     FILE *f;
10508     char string[MSG_SIZ];
10509
10510     if (   cmailMailedMove
10511         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10512         return TRUE;            /* Allow free viewing  */
10513     }
10514
10515     /* Unregister move to ensure that we don't leave RegisterMove        */
10516     /* with the move registered when the conditions for registering no   */
10517     /* longer hold                                                       */
10518     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10519         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10520         nCmailMovesRegistered --;
10521
10522         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10523           {
10524               free(cmailCommentList[lastLoadGameNumber - 1]);
10525               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10526           }
10527     }
10528
10529     if (cmailOldMove == -1) {
10530         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10531         return FALSE;
10532     }
10533
10534     if (currentMove > cmailOldMove + 1) {
10535         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10536         return FALSE;
10537     }
10538
10539     if (currentMove < cmailOldMove) {
10540         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10541         return FALSE;
10542     }
10543
10544     if (forwardMostMove > currentMove) {
10545         /* Silently truncate extra moves */
10546         TruncateGame();
10547     }
10548
10549     if (   (currentMove == cmailOldMove + 1)
10550         || (   (currentMove == cmailOldMove)
10551             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10552                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10553         if (gameInfo.result != GameUnfinished) {
10554             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10555         }
10556
10557         if (commentList[currentMove] != NULL) {
10558             cmailCommentList[lastLoadGameNumber - 1]
10559               = StrSave(commentList[currentMove]);
10560         }
10561         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10562
10563         if (appData.debugMode)
10564           fprintf(debugFP, "Saving %s for game %d\n",
10565                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10566
10567         sprintf(string,
10568                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10569         
10570         f = fopen(string, "w");
10571         if (appData.oldSaveStyle) {
10572             SaveGameOldStyle(f); /* also closes the file */
10573             
10574             sprintf(string, "%s.pos.out", appData.cmailGameName);
10575             f = fopen(string, "w");
10576             SavePosition(f, 0, NULL); /* also closes the file */
10577         } else {
10578             fprintf(f, "{--------------\n");
10579             PrintPosition(f, currentMove);
10580             fprintf(f, "--------------}\n\n");
10581             
10582             SaveGame(f, 0, NULL); /* also closes the file*/
10583         }
10584         
10585         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10586         nCmailMovesRegistered ++;
10587     } else if (nCmailGames == 1) {
10588         DisplayError(_("You have not made a move yet"), 0);
10589         return FALSE;
10590     }
10591
10592     return TRUE;
10593 }
10594
10595 void
10596 MailMoveEvent()
10597 {
10598 #if !WIN32
10599     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10600     FILE *commandOutput;
10601     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10602     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10603     int nBuffers;
10604     int i;
10605     int archived;
10606     char *arcDir;
10607
10608     if (! cmailMsgLoaded) {
10609         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10610         return;
10611     }
10612
10613     if (nCmailGames == nCmailResults) {
10614         DisplayError(_("No unfinished games"), 0);
10615         return;
10616     }
10617
10618 #if CMAIL_PROHIBIT_REMAIL
10619     if (cmailMailedMove) {
10620         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);
10621         DisplayError(msg, 0);
10622         return;
10623     }
10624 #endif
10625
10626     if (! (cmailMailedMove || RegisterMove())) return;
10627     
10628     if (   cmailMailedMove
10629         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10630         sprintf(string, partCommandString,
10631                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10632         commandOutput = popen(string, "r");
10633
10634         if (commandOutput == NULL) {
10635             DisplayError(_("Failed to invoke cmail"), 0);
10636         } else {
10637             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10638                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10639             }
10640             if (nBuffers > 1) {
10641                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10642                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10643                 nBytes = MSG_SIZ - 1;
10644             } else {
10645                 (void) memcpy(msg, buffer, nBytes);
10646             }
10647             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10648
10649             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10650                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10651
10652                 archived = TRUE;
10653                 for (i = 0; i < nCmailGames; i ++) {
10654                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10655                         archived = FALSE;
10656                     }
10657                 }
10658                 if (   archived
10659                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10660                         != NULL)) {
10661                     sprintf(buffer, "%s/%s.%s.archive",
10662                             arcDir,
10663                             appData.cmailGameName,
10664                             gameInfo.date);
10665                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10666                     cmailMsgLoaded = FALSE;
10667                 }
10668             }
10669
10670             DisplayInformation(msg);
10671             pclose(commandOutput);
10672         }
10673     } else {
10674         if ((*cmailMsg) != '\0') {
10675             DisplayInformation(cmailMsg);
10676         }
10677     }
10678
10679     return;
10680 #endif /* !WIN32 */
10681 }
10682
10683 char *
10684 CmailMsg()
10685 {
10686 #if WIN32
10687     return NULL;
10688 #else
10689     int  prependComma = 0;
10690     char number[5];
10691     char string[MSG_SIZ];       /* Space for game-list */
10692     int  i;
10693     
10694     if (!cmailMsgLoaded) return "";
10695
10696     if (cmailMailedMove) {
10697         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10698     } else {
10699         /* Create a list of games left */
10700         sprintf(string, "[");
10701         for (i = 0; i < nCmailGames; i ++) {
10702             if (! (   cmailMoveRegistered[i]
10703                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10704                 if (prependComma) {
10705                     sprintf(number, ",%d", i + 1);
10706                 } else {
10707                     sprintf(number, "%d", i + 1);
10708                     prependComma = 1;
10709                 }
10710                 
10711                 strcat(string, number);
10712             }
10713         }
10714         strcat(string, "]");
10715
10716         if (nCmailMovesRegistered + nCmailResults == 0) {
10717             switch (nCmailGames) {
10718               case 1:
10719                 sprintf(cmailMsg,
10720                         _("Still need to make move for game\n"));
10721                 break;
10722                 
10723               case 2:
10724                 sprintf(cmailMsg,
10725                         _("Still need to make moves for both games\n"));
10726                 break;
10727                 
10728               default:
10729                 sprintf(cmailMsg,
10730                         _("Still need to make moves for all %d games\n"),
10731                         nCmailGames);
10732                 break;
10733             }
10734         } else {
10735             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10736               case 1:
10737                 sprintf(cmailMsg,
10738                         _("Still need to make a move for game %s\n"),
10739                         string);
10740                 break;
10741                 
10742               case 0:
10743                 if (nCmailResults == nCmailGames) {
10744                     sprintf(cmailMsg, _("No unfinished games\n"));
10745                 } else {
10746                     sprintf(cmailMsg, _("Ready to send mail\n"));
10747                 }
10748                 break;
10749                 
10750               default:
10751                 sprintf(cmailMsg,
10752                         _("Still need to make moves for games %s\n"),
10753                         string);
10754             }
10755         }
10756     }
10757     return cmailMsg;
10758 #endif /* WIN32 */
10759 }
10760
10761 void
10762 ResetGameEvent()
10763 {
10764     if (gameMode == Training)
10765       SetTrainingModeOff();
10766
10767     Reset(TRUE, TRUE);
10768     cmailMsgLoaded = FALSE;
10769     if (appData.icsActive) {
10770       SendToICS(ics_prefix);
10771       SendToICS("refresh\n");
10772     }
10773 }
10774
10775 void
10776 ExitEvent(status)
10777      int status;
10778 {
10779     exiting++;
10780     if (exiting > 2) {
10781       /* Give up on clean exit */
10782       exit(status);
10783     }
10784     if (exiting > 1) {
10785       /* Keep trying for clean exit */
10786       return;
10787     }
10788
10789     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10790
10791     if (telnetISR != NULL) {
10792       RemoveInputSource(telnetISR);
10793     }
10794     if (icsPR != NoProc) {
10795       DestroyChildProcess(icsPR, TRUE);
10796     }
10797
10798     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10799     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10800
10801     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10802     /* make sure this other one finishes before killing it!                  */
10803     if(endingGame) { int count = 0;
10804         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10805         while(endingGame && count++ < 10) DoSleep(1);
10806         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10807     }
10808
10809     /* Kill off chess programs */
10810     if (first.pr != NoProc) {
10811         ExitAnalyzeMode();
10812         
10813         DoSleep( appData.delayBeforeQuit );
10814         SendToProgram("quit\n", &first);
10815         DoSleep( appData.delayAfterQuit );
10816         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10817     }
10818     if (second.pr != NoProc) {
10819         DoSleep( appData.delayBeforeQuit );
10820         SendToProgram("quit\n", &second);
10821         DoSleep( appData.delayAfterQuit );
10822         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10823     }
10824     if (first.isr != NULL) {
10825         RemoveInputSource(first.isr);
10826     }
10827     if (second.isr != NULL) {
10828         RemoveInputSource(second.isr);
10829     }
10830
10831     ShutDownFrontEnd();
10832     exit(status);
10833 }
10834
10835 void
10836 PauseEvent()
10837 {
10838     if (appData.debugMode)
10839         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10840     if (pausing) {
10841         pausing = FALSE;
10842         ModeHighlight();
10843         if (gameMode == MachinePlaysWhite ||
10844             gameMode == MachinePlaysBlack) {
10845             StartClocks();
10846         } else {
10847             DisplayBothClocks();
10848         }
10849         if (gameMode == PlayFromGameFile) {
10850             if (appData.timeDelay >= 0) 
10851                 AutoPlayGameLoop();
10852         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10853             Reset(FALSE, TRUE);
10854             SendToICS(ics_prefix);
10855             SendToICS("refresh\n");
10856         } else if (currentMove < forwardMostMove) {
10857             ForwardInner(forwardMostMove);
10858         }
10859         pauseExamInvalid = FALSE;
10860     } else {
10861         switch (gameMode) {
10862           default:
10863             return;
10864           case IcsExamining:
10865             pauseExamForwardMostMove = forwardMostMove;
10866             pauseExamInvalid = FALSE;
10867             /* fall through */
10868           case IcsObserving:
10869           case IcsPlayingWhite:
10870           case IcsPlayingBlack:
10871             pausing = TRUE;
10872             ModeHighlight();
10873             return;
10874           case PlayFromGameFile:
10875             (void) StopLoadGameTimer();
10876             pausing = TRUE;
10877             ModeHighlight();
10878             break;
10879           case BeginningOfGame:
10880             if (appData.icsActive) return;
10881             /* else fall through */
10882           case MachinePlaysWhite:
10883           case MachinePlaysBlack:
10884           case TwoMachinesPlay:
10885             if (forwardMostMove == 0)
10886               return;           /* don't pause if no one has moved */
10887             if ((gameMode == MachinePlaysWhite &&
10888                  !WhiteOnMove(forwardMostMove)) ||
10889                 (gameMode == MachinePlaysBlack &&
10890                  WhiteOnMove(forwardMostMove))) {
10891                 StopClocks();
10892             }
10893             pausing = TRUE;
10894             ModeHighlight();
10895             break;
10896         }
10897     }
10898 }
10899
10900 void
10901 EditCommentEvent()
10902 {
10903     char title[MSG_SIZ];
10904
10905     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10906         strcpy(title, _("Edit comment"));
10907     } else {
10908         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10909                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10910                 parseList[currentMove - 1]);
10911     }
10912
10913     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10914 }
10915
10916
10917 void
10918 EditTagsEvent()
10919 {
10920     char *tags = PGNTags(&gameInfo);
10921     EditTagsPopUp(tags);
10922     free(tags);
10923 }
10924
10925 void
10926 AnalyzeModeEvent()
10927 {
10928     if (appData.noChessProgram || gameMode == AnalyzeMode)
10929       return;
10930
10931     if (gameMode != AnalyzeFile) {
10932         if (!appData.icsEngineAnalyze) {
10933                EditGameEvent();
10934                if (gameMode != EditGame) return;
10935         }
10936         ResurrectChessProgram();
10937         SendToProgram("analyze\n", &first);
10938         first.analyzing = TRUE;
10939         /*first.maybeThinking = TRUE;*/
10940         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10941         EngineOutputPopUp();
10942     }
10943     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10944     pausing = FALSE;
10945     ModeHighlight();
10946     SetGameInfo();
10947
10948     StartAnalysisClock();
10949     GetTimeMark(&lastNodeCountTime);
10950     lastNodeCount = 0;
10951 }
10952
10953 void
10954 AnalyzeFileEvent()
10955 {
10956     if (appData.noChessProgram || gameMode == AnalyzeFile)
10957       return;
10958
10959     if (gameMode != AnalyzeMode) {
10960         EditGameEvent();
10961         if (gameMode != EditGame) return;
10962         ResurrectChessProgram();
10963         SendToProgram("analyze\n", &first);
10964         first.analyzing = TRUE;
10965         /*first.maybeThinking = TRUE;*/
10966         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10967         EngineOutputPopUp();
10968     }
10969     gameMode = AnalyzeFile;
10970     pausing = FALSE;
10971     ModeHighlight();
10972     SetGameInfo();
10973
10974     StartAnalysisClock();
10975     GetTimeMark(&lastNodeCountTime);
10976     lastNodeCount = 0;
10977 }
10978
10979 void
10980 MachineWhiteEvent()
10981 {
10982     char buf[MSG_SIZ];
10983     char *bookHit = NULL;
10984
10985     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10986       return;
10987
10988
10989     if (gameMode == PlayFromGameFile || 
10990         gameMode == TwoMachinesPlay  || 
10991         gameMode == Training         || 
10992         gameMode == AnalyzeMode      || 
10993         gameMode == EndOfGame)
10994         EditGameEvent();
10995
10996     if (gameMode == EditPosition) 
10997         EditPositionDone(TRUE);
10998
10999     if (!WhiteOnMove(currentMove)) {
11000         DisplayError(_("It is not White's turn"), 0);
11001         return;
11002     }
11003   
11004     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11005       ExitAnalyzeMode();
11006
11007     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11008         gameMode == AnalyzeFile)
11009         TruncateGame();
11010
11011     ResurrectChessProgram();    /* in case it isn't running */
11012     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11013         gameMode = MachinePlaysWhite;
11014         ResetClocks();
11015     } else
11016     gameMode = MachinePlaysWhite;
11017     pausing = FALSE;
11018     ModeHighlight();
11019     SetGameInfo();
11020     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11021     DisplayTitle(buf);
11022     if (first.sendName) {
11023       sprintf(buf, "name %s\n", gameInfo.black);
11024       SendToProgram(buf, &first);
11025     }
11026     if (first.sendTime) {
11027       if (first.useColors) {
11028         SendToProgram("black\n", &first); /*gnu kludge*/
11029       }
11030       SendTimeRemaining(&first, TRUE);
11031     }
11032     if (first.useColors) {
11033       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11034     }
11035     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11036     SetMachineThinkingEnables();
11037     first.maybeThinking = TRUE;
11038     StartClocks();
11039     firstMove = FALSE;
11040
11041     if (appData.autoFlipView && !flipView) {
11042       flipView = !flipView;
11043       DrawPosition(FALSE, NULL);
11044       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11045     }
11046
11047     if(bookHit) { // [HGM] book: simulate book reply
11048         static char bookMove[MSG_SIZ]; // a bit generous?
11049
11050         programStats.nodes = programStats.depth = programStats.time = 
11051         programStats.score = programStats.got_only_move = 0;
11052         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11053
11054         strcpy(bookMove, "move ");
11055         strcat(bookMove, bookHit);
11056         HandleMachineMove(bookMove, &first);
11057     }
11058 }
11059
11060 void
11061 MachineBlackEvent()
11062 {
11063     char buf[MSG_SIZ];
11064    char *bookHit = NULL;
11065
11066     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11067         return;
11068
11069
11070     if (gameMode == PlayFromGameFile || 
11071         gameMode == TwoMachinesPlay  || 
11072         gameMode == Training         || 
11073         gameMode == AnalyzeMode      || 
11074         gameMode == EndOfGame)
11075         EditGameEvent();
11076
11077     if (gameMode == EditPosition) 
11078         EditPositionDone(TRUE);
11079
11080     if (WhiteOnMove(currentMove)) {
11081         DisplayError(_("It is not Black's turn"), 0);
11082         return;
11083     }
11084     
11085     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11086       ExitAnalyzeMode();
11087
11088     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11089         gameMode == AnalyzeFile)
11090         TruncateGame();
11091
11092     ResurrectChessProgram();    /* in case it isn't running */
11093     gameMode = MachinePlaysBlack;
11094     pausing = FALSE;
11095     ModeHighlight();
11096     SetGameInfo();
11097     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11098     DisplayTitle(buf);
11099     if (first.sendName) {
11100       sprintf(buf, "name %s\n", gameInfo.white);
11101       SendToProgram(buf, &first);
11102     }
11103     if (first.sendTime) {
11104       if (first.useColors) {
11105         SendToProgram("white\n", &first); /*gnu kludge*/
11106       }
11107       SendTimeRemaining(&first, FALSE);
11108     }
11109     if (first.useColors) {
11110       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11111     }
11112     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11113     SetMachineThinkingEnables();
11114     first.maybeThinking = TRUE;
11115     StartClocks();
11116
11117     if (appData.autoFlipView && flipView) {
11118       flipView = !flipView;
11119       DrawPosition(FALSE, NULL);
11120       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11121     }
11122     if(bookHit) { // [HGM] book: simulate book reply
11123         static char bookMove[MSG_SIZ]; // a bit generous?
11124
11125         programStats.nodes = programStats.depth = programStats.time = 
11126         programStats.score = programStats.got_only_move = 0;
11127         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11128
11129         strcpy(bookMove, "move ");
11130         strcat(bookMove, bookHit);
11131         HandleMachineMove(bookMove, &first);
11132     }
11133 }
11134
11135
11136 void
11137 DisplayTwoMachinesTitle()
11138 {
11139     char buf[MSG_SIZ];
11140     if (appData.matchGames > 0) {
11141         if (first.twoMachinesColor[0] == 'w') {
11142             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11143                     gameInfo.white, gameInfo.black,
11144                     first.matchWins, second.matchWins,
11145                     matchGame - 1 - (first.matchWins + second.matchWins));
11146         } else {
11147             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11148                     gameInfo.white, gameInfo.black,
11149                     second.matchWins, first.matchWins,
11150                     matchGame - 1 - (first.matchWins + second.matchWins));
11151         }
11152     } else {
11153         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11154     }
11155     DisplayTitle(buf);
11156 }
11157
11158 void
11159 TwoMachinesEvent P((void))
11160 {
11161     int i;
11162     char buf[MSG_SIZ];
11163     ChessProgramState *onmove;
11164     char *bookHit = NULL;
11165     
11166     if (appData.noChessProgram) return;
11167
11168     switch (gameMode) {
11169       case TwoMachinesPlay:
11170         return;
11171       case MachinePlaysWhite:
11172       case MachinePlaysBlack:
11173         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11174             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11175             return;
11176         }
11177         /* fall through */
11178       case BeginningOfGame:
11179       case PlayFromGameFile:
11180       case EndOfGame:
11181         EditGameEvent();
11182         if (gameMode != EditGame) return;
11183         break;
11184       case EditPosition:
11185         EditPositionDone(TRUE);
11186         break;
11187       case AnalyzeMode:
11188       case AnalyzeFile:
11189         ExitAnalyzeMode();
11190         break;
11191       case EditGame:
11192       default:
11193         break;
11194     }
11195
11196 //    forwardMostMove = currentMove;
11197     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11198     ResurrectChessProgram();    /* in case first program isn't running */
11199
11200     if (second.pr == NULL) {
11201         StartChessProgram(&second);
11202         if (second.protocolVersion == 1) {
11203           TwoMachinesEventIfReady();
11204         } else {
11205           /* kludge: allow timeout for initial "feature" command */
11206           FreezeUI();
11207           DisplayMessage("", _("Starting second chess program"));
11208           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11209         }
11210         return;
11211     }
11212     DisplayMessage("", "");
11213     InitChessProgram(&second, FALSE);
11214     SendToProgram("force\n", &second);
11215     if (startedFromSetupPosition) {
11216         SendBoard(&second, backwardMostMove);
11217     if (appData.debugMode) {
11218         fprintf(debugFP, "Two Machines\n");
11219     }
11220     }
11221     for (i = backwardMostMove; i < forwardMostMove; i++) {
11222         SendMoveToProgram(i, &second);
11223     }
11224
11225     gameMode = TwoMachinesPlay;
11226     pausing = FALSE;
11227     ModeHighlight();
11228     SetGameInfo();
11229     DisplayTwoMachinesTitle();
11230     firstMove = TRUE;
11231     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11232         onmove = &first;
11233     } else {
11234         onmove = &second;
11235     }
11236
11237     SendToProgram(first.computerString, &first);
11238     if (first.sendName) {
11239       sprintf(buf, "name %s\n", second.tidy);
11240       SendToProgram(buf, &first);
11241     }
11242     SendToProgram(second.computerString, &second);
11243     if (second.sendName) {
11244       sprintf(buf, "name %s\n", first.tidy);
11245       SendToProgram(buf, &second);
11246     }
11247
11248     ResetClocks();
11249     if (!first.sendTime || !second.sendTime) {
11250         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11251         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11252     }
11253     if (onmove->sendTime) {
11254       if (onmove->useColors) {
11255         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11256       }
11257       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11258     }
11259     if (onmove->useColors) {
11260       SendToProgram(onmove->twoMachinesColor, onmove);
11261     }
11262     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11263 //    SendToProgram("go\n", onmove);
11264     onmove->maybeThinking = TRUE;
11265     SetMachineThinkingEnables();
11266
11267     StartClocks();
11268
11269     if(bookHit) { // [HGM] book: simulate book reply
11270         static char bookMove[MSG_SIZ]; // a bit generous?
11271
11272         programStats.nodes = programStats.depth = programStats.time = 
11273         programStats.score = programStats.got_only_move = 0;
11274         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11275
11276         strcpy(bookMove, "move ");
11277         strcat(bookMove, bookHit);
11278         savedMessage = bookMove; // args for deferred call
11279         savedState = onmove;
11280         ScheduleDelayedEvent(DeferredBookMove, 1);
11281     }
11282 }
11283
11284 void
11285 TrainingEvent()
11286 {
11287     if (gameMode == Training) {
11288       SetTrainingModeOff();
11289       gameMode = PlayFromGameFile;
11290       DisplayMessage("", _("Training mode off"));
11291     } else {
11292       gameMode = Training;
11293       animateTraining = appData.animate;
11294
11295       /* make sure we are not already at the end of the game */
11296       if (currentMove < forwardMostMove) {
11297         SetTrainingModeOn();
11298         DisplayMessage("", _("Training mode on"));
11299       } else {
11300         gameMode = PlayFromGameFile;
11301         DisplayError(_("Already at end of game"), 0);
11302       }
11303     }
11304     ModeHighlight();
11305 }
11306
11307 void
11308 IcsClientEvent()
11309 {
11310     if (!appData.icsActive) return;
11311     switch (gameMode) {
11312       case IcsPlayingWhite:
11313       case IcsPlayingBlack:
11314       case IcsObserving:
11315       case IcsIdle:
11316       case BeginningOfGame:
11317       case IcsExamining:
11318         return;
11319
11320       case EditGame:
11321         break;
11322
11323       case EditPosition:
11324         EditPositionDone(TRUE);
11325         break;
11326
11327       case AnalyzeMode:
11328       case AnalyzeFile:
11329         ExitAnalyzeMode();
11330         break;
11331         
11332       default:
11333         EditGameEvent();
11334         break;
11335     }
11336
11337     gameMode = IcsIdle;
11338     ModeHighlight();
11339     return;
11340 }
11341
11342
11343 void
11344 EditGameEvent()
11345 {
11346     int i;
11347
11348     switch (gameMode) {
11349       case Training:
11350         SetTrainingModeOff();
11351         break;
11352       case MachinePlaysWhite:
11353       case MachinePlaysBlack:
11354       case BeginningOfGame:
11355         SendToProgram("force\n", &first);
11356         SetUserThinkingEnables();
11357         break;
11358       case PlayFromGameFile:
11359         (void) StopLoadGameTimer();
11360         if (gameFileFP != NULL) {
11361             gameFileFP = NULL;
11362         }
11363         break;
11364       case EditPosition:
11365         EditPositionDone(TRUE);
11366         break;
11367       case AnalyzeMode:
11368       case AnalyzeFile:
11369         ExitAnalyzeMode();
11370         SendToProgram("force\n", &first);
11371         break;
11372       case TwoMachinesPlay:
11373         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11374         ResurrectChessProgram();
11375         SetUserThinkingEnables();
11376         break;
11377       case EndOfGame:
11378         ResurrectChessProgram();
11379         break;
11380       case IcsPlayingBlack:
11381       case IcsPlayingWhite:
11382         DisplayError(_("Warning: You are still playing a game"), 0);
11383         break;
11384       case IcsObserving:
11385         DisplayError(_("Warning: You are still observing a game"), 0);
11386         break;
11387       case IcsExamining:
11388         DisplayError(_("Warning: You are still examining a game"), 0);
11389         break;
11390       case IcsIdle:
11391         break;
11392       case EditGame:
11393       default:
11394         return;
11395     }
11396     
11397     pausing = FALSE;
11398     StopClocks();
11399     first.offeredDraw = second.offeredDraw = 0;
11400
11401     if (gameMode == PlayFromGameFile) {
11402         whiteTimeRemaining = timeRemaining[0][currentMove];
11403         blackTimeRemaining = timeRemaining[1][currentMove];
11404         DisplayTitle("");
11405     }
11406
11407     if (gameMode == MachinePlaysWhite ||
11408         gameMode == MachinePlaysBlack ||
11409         gameMode == TwoMachinesPlay ||
11410         gameMode == EndOfGame) {
11411         i = forwardMostMove;
11412         while (i > currentMove) {
11413             SendToProgram("undo\n", &first);
11414             i--;
11415         }
11416         whiteTimeRemaining = timeRemaining[0][currentMove];
11417         blackTimeRemaining = timeRemaining[1][currentMove];
11418         DisplayBothClocks();
11419         if (whiteFlag || blackFlag) {
11420             whiteFlag = blackFlag = 0;
11421         }
11422         DisplayTitle("");
11423     }           
11424     
11425     gameMode = EditGame;
11426     ModeHighlight();
11427     SetGameInfo();
11428 }
11429
11430
11431 void
11432 EditPositionEvent()
11433 {
11434     if (gameMode == EditPosition) {
11435         EditGameEvent();
11436         return;
11437     }
11438     
11439     EditGameEvent();
11440     if (gameMode != EditGame) return;
11441     
11442     gameMode = EditPosition;
11443     ModeHighlight();
11444     SetGameInfo();
11445     if (currentMove > 0)
11446       CopyBoard(boards[0], boards[currentMove]);
11447     
11448     blackPlaysFirst = !WhiteOnMove(currentMove);
11449     ResetClocks();
11450     currentMove = forwardMostMove = backwardMostMove = 0;
11451     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11452     DisplayMove(-1);
11453 }
11454
11455 void
11456 ExitAnalyzeMode()
11457 {
11458     /* [DM] icsEngineAnalyze - possible call from other functions */
11459     if (appData.icsEngineAnalyze) {
11460         appData.icsEngineAnalyze = FALSE;
11461
11462         DisplayMessage("",_("Close ICS engine analyze..."));
11463     }
11464     if (first.analysisSupport && first.analyzing) {
11465       SendToProgram("exit\n", &first);
11466       first.analyzing = FALSE;
11467     }
11468     thinkOutput[0] = NULLCHAR;
11469 }
11470
11471 void
11472 EditPositionDone(Boolean fakeRights)
11473 {
11474     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11475
11476     startedFromSetupPosition = TRUE;
11477     InitChessProgram(&first, FALSE);
11478     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11479       boards[0][EP_STATUS] = EP_NONE;
11480       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11481     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11482         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11483         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11484       } else boards[0][CASTLING][2] = NoRights;
11485     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11486         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11487         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11488       } else boards[0][CASTLING][5] = NoRights;
11489     }
11490     SendToProgram("force\n", &first);
11491     if (blackPlaysFirst) {
11492         strcpy(moveList[0], "");
11493         strcpy(parseList[0], "");
11494         currentMove = forwardMostMove = backwardMostMove = 1;
11495         CopyBoard(boards[1], boards[0]);
11496     } else {
11497         currentMove = forwardMostMove = backwardMostMove = 0;
11498     }
11499     SendBoard(&first, forwardMostMove);
11500     if (appData.debugMode) {
11501         fprintf(debugFP, "EditPosDone\n");
11502     }
11503     DisplayTitle("");
11504     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11505     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11506     gameMode = EditGame;
11507     ModeHighlight();
11508     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11509     ClearHighlights(); /* [AS] */
11510 }
11511
11512 /* Pause for `ms' milliseconds */
11513 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11514 void
11515 TimeDelay(ms)
11516      long ms;
11517 {
11518     TimeMark m1, m2;
11519
11520     GetTimeMark(&m1);
11521     do {
11522         GetTimeMark(&m2);
11523     } while (SubtractTimeMarks(&m2, &m1) < ms);
11524 }
11525
11526 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11527 void
11528 SendMultiLineToICS(buf)
11529      char *buf;
11530 {
11531     char temp[MSG_SIZ+1], *p;
11532     int len;
11533
11534     len = strlen(buf);
11535     if (len > MSG_SIZ)
11536       len = MSG_SIZ;
11537   
11538     strncpy(temp, buf, len);
11539     temp[len] = 0;
11540
11541     p = temp;
11542     while (*p) {
11543         if (*p == '\n' || *p == '\r')
11544           *p = ' ';
11545         ++p;
11546     }
11547
11548     strcat(temp, "\n");
11549     SendToICS(temp);
11550     SendToPlayer(temp, strlen(temp));
11551 }
11552
11553 void
11554 SetWhiteToPlayEvent()
11555 {
11556     if (gameMode == EditPosition) {
11557         blackPlaysFirst = FALSE;
11558         DisplayBothClocks();    /* works because currentMove is 0 */
11559     } else if (gameMode == IcsExamining) {
11560         SendToICS(ics_prefix);
11561         SendToICS("tomove white\n");
11562     }
11563 }
11564
11565 void
11566 SetBlackToPlayEvent()
11567 {
11568     if (gameMode == EditPosition) {
11569         blackPlaysFirst = TRUE;
11570         currentMove = 1;        /* kludge */
11571         DisplayBothClocks();
11572         currentMove = 0;
11573     } else if (gameMode == IcsExamining) {
11574         SendToICS(ics_prefix);
11575         SendToICS("tomove black\n");
11576     }
11577 }
11578
11579 void
11580 EditPositionMenuEvent(selection, x, y)
11581      ChessSquare selection;
11582      int x, y;
11583 {
11584     char buf[MSG_SIZ];
11585     ChessSquare piece = boards[0][y][x];
11586
11587     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11588
11589     switch (selection) {
11590       case ClearBoard:
11591         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11592             SendToICS(ics_prefix);
11593             SendToICS("bsetup clear\n");
11594         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11595             SendToICS(ics_prefix);
11596             SendToICS("clearboard\n");
11597         } else {
11598             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11599                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11600                 for (y = 0; y < BOARD_HEIGHT; y++) {
11601                     if (gameMode == IcsExamining) {
11602                         if (boards[currentMove][y][x] != EmptySquare) {
11603                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11604                                     AAA + x, ONE + y);
11605                             SendToICS(buf);
11606                         }
11607                     } else {
11608                         boards[0][y][x] = p;
11609                     }
11610                 }
11611             }
11612         }
11613         if (gameMode == EditPosition) {
11614             DrawPosition(FALSE, boards[0]);
11615         }
11616         break;
11617
11618       case WhitePlay:
11619         SetWhiteToPlayEvent();
11620         break;
11621
11622       case BlackPlay:
11623         SetBlackToPlayEvent();
11624         break;
11625
11626       case EmptySquare:
11627         if (gameMode == IcsExamining) {
11628             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11629             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11630             SendToICS(buf);
11631         } else {
11632             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11633                 if(x == BOARD_LEFT-2) {
11634                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11635                     boards[0][y][1] = 0;
11636                 } else
11637                 if(x == BOARD_RGHT+1) {
11638                     if(y >= gameInfo.holdingsSize) break;
11639                     boards[0][y][BOARD_WIDTH-2] = 0;
11640                 } else break;
11641             }
11642             boards[0][y][x] = EmptySquare;
11643             DrawPosition(FALSE, boards[0]);
11644         }
11645         break;
11646
11647       case PromotePiece:
11648         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11649            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11650             selection = (ChessSquare) (PROMOTED piece);
11651         } else if(piece == EmptySquare) selection = WhiteSilver;
11652         else selection = (ChessSquare)((int)piece - 1);
11653         goto defaultlabel;
11654
11655       case DemotePiece:
11656         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11657            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11658             selection = (ChessSquare) (DEMOTED piece);
11659         } else if(piece == EmptySquare) selection = BlackSilver;
11660         else selection = (ChessSquare)((int)piece + 1);       
11661         goto defaultlabel;
11662
11663       case WhiteQueen:
11664       case BlackQueen:
11665         if(gameInfo.variant == VariantShatranj ||
11666            gameInfo.variant == VariantXiangqi  ||
11667            gameInfo.variant == VariantCourier  ||
11668            gameInfo.variant == VariantMakruk     )
11669             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11670         goto defaultlabel;
11671
11672       case WhiteKing:
11673       case BlackKing:
11674         if(gameInfo.variant == VariantXiangqi)
11675             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11676         if(gameInfo.variant == VariantKnightmate)
11677             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11678       default:
11679         defaultlabel:
11680         if (gameMode == IcsExamining) {
11681             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11682             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11683                     PieceToChar(selection), AAA + x, ONE + y);
11684             SendToICS(buf);
11685         } else {
11686             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11687                 int n;
11688                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11689                     n = PieceToNumber(selection - BlackPawn);
11690                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11691                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11692                     boards[0][BOARD_HEIGHT-1-n][1]++;
11693                 } else
11694                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11695                     n = PieceToNumber(selection);
11696                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11697                     boards[0][n][BOARD_WIDTH-1] = selection;
11698                     boards[0][n][BOARD_WIDTH-2]++;
11699                 }
11700             } else
11701             boards[0][y][x] = selection;
11702             DrawPosition(TRUE, boards[0]);
11703         }
11704         break;
11705     }
11706 }
11707
11708
11709 void
11710 DropMenuEvent(selection, x, y)
11711      ChessSquare selection;
11712      int x, y;
11713 {
11714     ChessMove moveType;
11715
11716     switch (gameMode) {
11717       case IcsPlayingWhite:
11718       case MachinePlaysBlack:
11719         if (!WhiteOnMove(currentMove)) {
11720             DisplayMoveError(_("It is Black's turn"));
11721             return;
11722         }
11723         moveType = WhiteDrop;
11724         break;
11725       case IcsPlayingBlack:
11726       case MachinePlaysWhite:
11727         if (WhiteOnMove(currentMove)) {
11728             DisplayMoveError(_("It is White's turn"));
11729             return;
11730         }
11731         moveType = BlackDrop;
11732         break;
11733       case EditGame:
11734         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11735         break;
11736       default:
11737         return;
11738     }
11739
11740     if (moveType == BlackDrop && selection < BlackPawn) {
11741       selection = (ChessSquare) ((int) selection
11742                                  + (int) BlackPawn - (int) WhitePawn);
11743     }
11744     if (boards[currentMove][y][x] != EmptySquare) {
11745         DisplayMoveError(_("That square is occupied"));
11746         return;
11747     }
11748
11749     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11750 }
11751
11752 void
11753 AcceptEvent()
11754 {
11755     /* Accept a pending offer of any kind from opponent */
11756     
11757     if (appData.icsActive) {
11758         SendToICS(ics_prefix);
11759         SendToICS("accept\n");
11760     } else if (cmailMsgLoaded) {
11761         if (currentMove == cmailOldMove &&
11762             commentList[cmailOldMove] != NULL &&
11763             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11764                    "Black offers a draw" : "White offers a draw")) {
11765             TruncateGame();
11766             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11767             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11768         } else {
11769             DisplayError(_("There is no pending offer on this move"), 0);
11770             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11771         }
11772     } else {
11773         /* Not used for offers from chess program */
11774     }
11775 }
11776
11777 void
11778 DeclineEvent()
11779 {
11780     /* Decline a pending offer of any kind from opponent */
11781     
11782     if (appData.icsActive) {
11783         SendToICS(ics_prefix);
11784         SendToICS("decline\n");
11785     } else if (cmailMsgLoaded) {
11786         if (currentMove == cmailOldMove &&
11787             commentList[cmailOldMove] != NULL &&
11788             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11789                    "Black offers a draw" : "White offers a draw")) {
11790 #ifdef NOTDEF
11791             AppendComment(cmailOldMove, "Draw declined", TRUE);
11792             DisplayComment(cmailOldMove - 1, "Draw declined");
11793 #endif /*NOTDEF*/
11794         } else {
11795             DisplayError(_("There is no pending offer on this move"), 0);
11796         }
11797     } else {
11798         /* Not used for offers from chess program */
11799     }
11800 }
11801
11802 void
11803 RematchEvent()
11804 {
11805     /* Issue ICS rematch command */
11806     if (appData.icsActive) {
11807         SendToICS(ics_prefix);
11808         SendToICS("rematch\n");
11809     }
11810 }
11811
11812 void
11813 CallFlagEvent()
11814 {
11815     /* Call your opponent's flag (claim a win on time) */
11816     if (appData.icsActive) {
11817         SendToICS(ics_prefix);
11818         SendToICS("flag\n");
11819     } else {
11820         switch (gameMode) {
11821           default:
11822             return;
11823           case MachinePlaysWhite:
11824             if (whiteFlag) {
11825                 if (blackFlag)
11826                   GameEnds(GameIsDrawn, "Both players ran out of time",
11827                            GE_PLAYER);
11828                 else
11829                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11830             } else {
11831                 DisplayError(_("Your opponent is not out of time"), 0);
11832             }
11833             break;
11834           case MachinePlaysBlack:
11835             if (blackFlag) {
11836                 if (whiteFlag)
11837                   GameEnds(GameIsDrawn, "Both players ran out of time",
11838                            GE_PLAYER);
11839                 else
11840                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11841             } else {
11842                 DisplayError(_("Your opponent is not out of time"), 0);
11843             }
11844             break;
11845         }
11846     }
11847 }
11848
11849 void
11850 DrawEvent()
11851 {
11852     /* Offer draw or accept pending draw offer from opponent */
11853     
11854     if (appData.icsActive) {
11855         /* Note: tournament rules require draw offers to be
11856            made after you make your move but before you punch
11857            your clock.  Currently ICS doesn't let you do that;
11858            instead, you immediately punch your clock after making
11859            a move, but you can offer a draw at any time. */
11860         
11861         SendToICS(ics_prefix);
11862         SendToICS("draw\n");
11863         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
11864     } else if (cmailMsgLoaded) {
11865         if (currentMove == cmailOldMove &&
11866             commentList[cmailOldMove] != NULL &&
11867             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11868                    "Black offers a draw" : "White offers a draw")) {
11869             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11870             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11871         } else if (currentMove == cmailOldMove + 1) {
11872             char *offer = WhiteOnMove(cmailOldMove) ?
11873               "White offers a draw" : "Black offers a draw";
11874             AppendComment(currentMove, offer, TRUE);
11875             DisplayComment(currentMove - 1, offer);
11876             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11877         } else {
11878             DisplayError(_("You must make your move before offering a draw"), 0);
11879             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11880         }
11881     } else if (first.offeredDraw) {
11882         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11883     } else {
11884         if (first.sendDrawOffers) {
11885             SendToProgram("draw\n", &first);
11886             userOfferedDraw = TRUE;
11887         }
11888     }
11889 }
11890
11891 void
11892 AdjournEvent()
11893 {
11894     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11895     
11896     if (appData.icsActive) {
11897         SendToICS(ics_prefix);
11898         SendToICS("adjourn\n");
11899     } else {
11900         /* Currently GNU Chess doesn't offer or accept Adjourns */
11901     }
11902 }
11903
11904
11905 void
11906 AbortEvent()
11907 {
11908     /* Offer Abort or accept pending Abort offer from opponent */
11909     
11910     if (appData.icsActive) {
11911         SendToICS(ics_prefix);
11912         SendToICS("abort\n");
11913     } else {
11914         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11915     }
11916 }
11917
11918 void
11919 ResignEvent()
11920 {
11921     /* Resign.  You can do this even if it's not your turn. */
11922     
11923     if (appData.icsActive) {
11924         SendToICS(ics_prefix);
11925         SendToICS("resign\n");
11926     } else {
11927         switch (gameMode) {
11928           case MachinePlaysWhite:
11929             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11930             break;
11931           case MachinePlaysBlack:
11932             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11933             break;
11934           case EditGame:
11935             if (cmailMsgLoaded) {
11936                 TruncateGame();
11937                 if (WhiteOnMove(cmailOldMove)) {
11938                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11939                 } else {
11940                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11941                 }
11942                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11943             }
11944             break;
11945           default:
11946             break;
11947         }
11948     }
11949 }
11950
11951
11952 void
11953 StopObservingEvent()
11954 {
11955     /* Stop observing current games */
11956     SendToICS(ics_prefix);
11957     SendToICS("unobserve\n");
11958 }
11959
11960 void
11961 StopExaminingEvent()
11962 {
11963     /* Stop observing current game */
11964     SendToICS(ics_prefix);
11965     SendToICS("unexamine\n");
11966 }
11967
11968 void
11969 ForwardInner(target)
11970      int target;
11971 {
11972     int limit;
11973
11974     if (appData.debugMode)
11975         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11976                 target, currentMove, forwardMostMove);
11977
11978     if (gameMode == EditPosition)
11979       return;
11980
11981     if (gameMode == PlayFromGameFile && !pausing)
11982       PauseEvent();
11983     
11984     if (gameMode == IcsExamining && pausing)
11985       limit = pauseExamForwardMostMove;
11986     else
11987       limit = forwardMostMove;
11988     
11989     if (target > limit) target = limit;
11990
11991     if (target > 0 && moveList[target - 1][0]) {
11992         int fromX, fromY, toX, toY;
11993         toX = moveList[target - 1][2] - AAA;
11994         toY = moveList[target - 1][3] - ONE;
11995         if (moveList[target - 1][1] == '@') {
11996             if (appData.highlightLastMove) {
11997                 SetHighlights(-1, -1, toX, toY);
11998             }
11999         } else {
12000             fromX = moveList[target - 1][0] - AAA;
12001             fromY = moveList[target - 1][1] - ONE;
12002             if (target == currentMove + 1) {
12003                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12004             }
12005             if (appData.highlightLastMove) {
12006                 SetHighlights(fromX, fromY, toX, toY);
12007             }
12008         }
12009     }
12010     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12011         gameMode == Training || gameMode == PlayFromGameFile || 
12012         gameMode == AnalyzeFile) {
12013         while (currentMove < target) {
12014             SendMoveToProgram(currentMove++, &first);
12015         }
12016     } else {
12017         currentMove = target;
12018     }
12019     
12020     if (gameMode == EditGame || gameMode == EndOfGame) {
12021         whiteTimeRemaining = timeRemaining[0][currentMove];
12022         blackTimeRemaining = timeRemaining[1][currentMove];
12023     }
12024     DisplayBothClocks();
12025     DisplayMove(currentMove - 1);
12026     DrawPosition(FALSE, boards[currentMove]);
12027     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12028     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12029         DisplayComment(currentMove - 1, commentList[currentMove]);
12030     }
12031 }
12032
12033
12034 void
12035 ForwardEvent()
12036 {
12037     if (gameMode == IcsExamining && !pausing) {
12038         SendToICS(ics_prefix);
12039         SendToICS("forward\n");
12040     } else {
12041         ForwardInner(currentMove + 1);
12042     }
12043 }
12044
12045 void
12046 ToEndEvent()
12047 {
12048     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12049         /* to optimze, we temporarily turn off analysis mode while we feed
12050          * the remaining moves to the engine. Otherwise we get analysis output
12051          * after each move.
12052          */ 
12053         if (first.analysisSupport) {
12054           SendToProgram("exit\nforce\n", &first);
12055           first.analyzing = FALSE;
12056         }
12057     }
12058         
12059     if (gameMode == IcsExamining && !pausing) {
12060         SendToICS(ics_prefix);
12061         SendToICS("forward 999999\n");
12062     } else {
12063         ForwardInner(forwardMostMove);
12064     }
12065
12066     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12067         /* we have fed all the moves, so reactivate analysis mode */
12068         SendToProgram("analyze\n", &first);
12069         first.analyzing = TRUE;
12070         /*first.maybeThinking = TRUE;*/
12071         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12072     }
12073 }
12074
12075 void
12076 BackwardInner(target)
12077      int target;
12078 {
12079     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12080
12081     if (appData.debugMode)
12082         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12083                 target, currentMove, forwardMostMove);
12084
12085     if (gameMode == EditPosition) return;
12086     if (currentMove <= backwardMostMove) {
12087         ClearHighlights();
12088         DrawPosition(full_redraw, boards[currentMove]);
12089         return;
12090     }
12091     if (gameMode == PlayFromGameFile && !pausing)
12092       PauseEvent();
12093     
12094     if (moveList[target][0]) {
12095         int fromX, fromY, toX, toY;
12096         toX = moveList[target][2] - AAA;
12097         toY = moveList[target][3] - ONE;
12098         if (moveList[target][1] == '@') {
12099             if (appData.highlightLastMove) {
12100                 SetHighlights(-1, -1, toX, toY);
12101             }
12102         } else {
12103             fromX = moveList[target][0] - AAA;
12104             fromY = moveList[target][1] - ONE;
12105             if (target == currentMove - 1) {
12106                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12107             }
12108             if (appData.highlightLastMove) {
12109                 SetHighlights(fromX, fromY, toX, toY);
12110             }
12111         }
12112     }
12113     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12114         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12115         while (currentMove > target) {
12116             SendToProgram("undo\n", &first);
12117             currentMove--;
12118         }
12119     } else {
12120         currentMove = target;
12121     }
12122     
12123     if (gameMode == EditGame || gameMode == EndOfGame) {
12124         whiteTimeRemaining = timeRemaining[0][currentMove];
12125         blackTimeRemaining = timeRemaining[1][currentMove];
12126     }
12127     DisplayBothClocks();
12128     DisplayMove(currentMove - 1);
12129     DrawPosition(full_redraw, boards[currentMove]);
12130     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12131     // [HGM] PV info: routine tests if comment empty
12132     DisplayComment(currentMove - 1, commentList[currentMove]);
12133 }
12134
12135 void
12136 BackwardEvent()
12137 {
12138     if (gameMode == IcsExamining && !pausing) {
12139         SendToICS(ics_prefix);
12140         SendToICS("backward\n");
12141     } else {
12142         BackwardInner(currentMove - 1);
12143     }
12144 }
12145
12146 void
12147 ToStartEvent()
12148 {
12149     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12150         /* to optimize, we temporarily turn off analysis mode while we undo
12151          * all the moves. Otherwise we get analysis output after each undo.
12152          */ 
12153         if (first.analysisSupport) {
12154           SendToProgram("exit\nforce\n", &first);
12155           first.analyzing = FALSE;
12156         }
12157     }
12158
12159     if (gameMode == IcsExamining && !pausing) {
12160         SendToICS(ics_prefix);
12161         SendToICS("backward 999999\n");
12162     } else {
12163         BackwardInner(backwardMostMove);
12164     }
12165
12166     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12167         /* we have fed all the moves, so reactivate analysis mode */
12168         SendToProgram("analyze\n", &first);
12169         first.analyzing = TRUE;
12170         /*first.maybeThinking = TRUE;*/
12171         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12172     }
12173 }
12174
12175 void
12176 ToNrEvent(int to)
12177 {
12178   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12179   if (to >= forwardMostMove) to = forwardMostMove;
12180   if (to <= backwardMostMove) to = backwardMostMove;
12181   if (to < currentMove) {
12182     BackwardInner(to);
12183   } else {
12184     ForwardInner(to);
12185   }
12186 }
12187
12188 void
12189 RevertEvent()
12190 {
12191     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12192         return;
12193     }
12194     if (gameMode != IcsExamining) {
12195         DisplayError(_("You are not examining a game"), 0);
12196         return;
12197     }
12198     if (pausing) {
12199         DisplayError(_("You can't revert while pausing"), 0);
12200         return;
12201     }
12202     SendToICS(ics_prefix);
12203     SendToICS("revert\n");
12204 }
12205
12206 void
12207 RetractMoveEvent()
12208 {
12209     switch (gameMode) {
12210       case MachinePlaysWhite:
12211       case MachinePlaysBlack:
12212         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12213             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12214             return;
12215         }
12216         if (forwardMostMove < 2) return;
12217         currentMove = forwardMostMove = forwardMostMove - 2;
12218         whiteTimeRemaining = timeRemaining[0][currentMove];
12219         blackTimeRemaining = timeRemaining[1][currentMove];
12220         DisplayBothClocks();
12221         DisplayMove(currentMove - 1);
12222         ClearHighlights();/*!! could figure this out*/
12223         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12224         SendToProgram("remove\n", &first);
12225         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12226         break;
12227
12228       case BeginningOfGame:
12229       default:
12230         break;
12231
12232       case IcsPlayingWhite:
12233       case IcsPlayingBlack:
12234         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12235             SendToICS(ics_prefix);
12236             SendToICS("takeback 2\n");
12237         } else {
12238             SendToICS(ics_prefix);
12239             SendToICS("takeback 1\n");
12240         }
12241         break;
12242     }
12243 }
12244
12245 void
12246 MoveNowEvent()
12247 {
12248     ChessProgramState *cps;
12249
12250     switch (gameMode) {
12251       case MachinePlaysWhite:
12252         if (!WhiteOnMove(forwardMostMove)) {
12253             DisplayError(_("It is your turn"), 0);
12254             return;
12255         }
12256         cps = &first;
12257         break;
12258       case MachinePlaysBlack:
12259         if (WhiteOnMove(forwardMostMove)) {
12260             DisplayError(_("It is your turn"), 0);
12261             return;
12262         }
12263         cps = &first;
12264         break;
12265       case TwoMachinesPlay:
12266         if (WhiteOnMove(forwardMostMove) ==
12267             (first.twoMachinesColor[0] == 'w')) {
12268             cps = &first;
12269         } else {
12270             cps = &second;
12271         }
12272         break;
12273       case BeginningOfGame:
12274       default:
12275         return;
12276     }
12277     SendToProgram("?\n", cps);
12278 }
12279
12280 void
12281 TruncateGameEvent()
12282 {
12283     EditGameEvent();
12284     if (gameMode != EditGame) return;
12285     TruncateGame();
12286 }
12287
12288 void
12289 TruncateGame()
12290 {
12291     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12292     if (forwardMostMove > currentMove) {
12293         if (gameInfo.resultDetails != NULL) {
12294             free(gameInfo.resultDetails);
12295             gameInfo.resultDetails = NULL;
12296             gameInfo.result = GameUnfinished;
12297         }
12298         forwardMostMove = currentMove;
12299         HistorySet(parseList, backwardMostMove, forwardMostMove,
12300                    currentMove-1);
12301     }
12302 }
12303
12304 void
12305 HintEvent()
12306 {
12307     if (appData.noChessProgram) return;
12308     switch (gameMode) {
12309       case MachinePlaysWhite:
12310         if (WhiteOnMove(forwardMostMove)) {
12311             DisplayError(_("Wait until your turn"), 0);
12312             return;
12313         }
12314         break;
12315       case BeginningOfGame:
12316       case MachinePlaysBlack:
12317         if (!WhiteOnMove(forwardMostMove)) {
12318             DisplayError(_("Wait until your turn"), 0);
12319             return;
12320         }
12321         break;
12322       default:
12323         DisplayError(_("No hint available"), 0);
12324         return;
12325     }
12326     SendToProgram("hint\n", &first);
12327     hintRequested = TRUE;
12328 }
12329
12330 void
12331 BookEvent()
12332 {
12333     if (appData.noChessProgram) return;
12334     switch (gameMode) {
12335       case MachinePlaysWhite:
12336         if (WhiteOnMove(forwardMostMove)) {
12337             DisplayError(_("Wait until your turn"), 0);
12338             return;
12339         }
12340         break;
12341       case BeginningOfGame:
12342       case MachinePlaysBlack:
12343         if (!WhiteOnMove(forwardMostMove)) {
12344             DisplayError(_("Wait until your turn"), 0);
12345             return;
12346         }
12347         break;
12348       case EditPosition:
12349         EditPositionDone(TRUE);
12350         break;
12351       case TwoMachinesPlay:
12352         return;
12353       default:
12354         break;
12355     }
12356     SendToProgram("bk\n", &first);
12357     bookOutput[0] = NULLCHAR;
12358     bookRequested = TRUE;
12359 }
12360
12361 void
12362 AboutGameEvent()
12363 {
12364     char *tags = PGNTags(&gameInfo);
12365     TagsPopUp(tags, CmailMsg());
12366     free(tags);
12367 }
12368
12369 /* end button procedures */
12370
12371 void
12372 PrintPosition(fp, move)
12373      FILE *fp;
12374      int move;
12375 {
12376     int i, j;
12377     
12378     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12379         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12380             char c = PieceToChar(boards[move][i][j]);
12381             fputc(c == 'x' ? '.' : c, fp);
12382             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12383         }
12384     }
12385     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12386       fprintf(fp, "white to play\n");
12387     else
12388       fprintf(fp, "black to play\n");
12389 }
12390
12391 void
12392 PrintOpponents(fp)
12393      FILE *fp;
12394 {
12395     if (gameInfo.white != NULL) {
12396         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12397     } else {
12398         fprintf(fp, "\n");
12399     }
12400 }
12401
12402 /* Find last component of program's own name, using some heuristics */
12403 void
12404 TidyProgramName(prog, host, buf)
12405      char *prog, *host, buf[MSG_SIZ];
12406 {
12407     char *p, *q;
12408     int local = (strcmp(host, "localhost") == 0);
12409     while (!local && (p = strchr(prog, ';')) != NULL) {
12410         p++;
12411         while (*p == ' ') p++;
12412         prog = p;
12413     }
12414     if (*prog == '"' || *prog == '\'') {
12415         q = strchr(prog + 1, *prog);
12416     } else {
12417         q = strchr(prog, ' ');
12418     }
12419     if (q == NULL) q = prog + strlen(prog);
12420     p = q;
12421     while (p >= prog && *p != '/' && *p != '\\') p--;
12422     p++;
12423     if(p == prog && *p == '"') p++;
12424     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12425     memcpy(buf, p, q - p);
12426     buf[q - p] = NULLCHAR;
12427     if (!local) {
12428         strcat(buf, "@");
12429         strcat(buf, host);
12430     }
12431 }
12432
12433 char *
12434 TimeControlTagValue()
12435 {
12436     char buf[MSG_SIZ];
12437     if (!appData.clockMode) {
12438         strcpy(buf, "-");
12439     } else if (movesPerSession > 0) {
12440         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12441     } else if (timeIncrement == 0) {
12442         sprintf(buf, "%ld", timeControl/1000);
12443     } else {
12444         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12445     }
12446     return StrSave(buf);
12447 }
12448
12449 void
12450 SetGameInfo()
12451 {
12452     /* This routine is used only for certain modes */
12453     VariantClass v = gameInfo.variant;
12454     ChessMove r = GameUnfinished;
12455     char *p = NULL;
12456
12457     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12458         r = gameInfo.result; 
12459         p = gameInfo.resultDetails; 
12460         gameInfo.resultDetails = NULL;
12461     }
12462     ClearGameInfo(&gameInfo);
12463     gameInfo.variant = v;
12464
12465     switch (gameMode) {
12466       case MachinePlaysWhite:
12467         gameInfo.event = StrSave( appData.pgnEventHeader );
12468         gameInfo.site = StrSave(HostName());
12469         gameInfo.date = PGNDate();
12470         gameInfo.round = StrSave("-");
12471         gameInfo.white = StrSave(first.tidy);
12472         gameInfo.black = StrSave(UserName());
12473         gameInfo.timeControl = TimeControlTagValue();
12474         break;
12475
12476       case MachinePlaysBlack:
12477         gameInfo.event = StrSave( appData.pgnEventHeader );
12478         gameInfo.site = StrSave(HostName());
12479         gameInfo.date = PGNDate();
12480         gameInfo.round = StrSave("-");
12481         gameInfo.white = StrSave(UserName());
12482         gameInfo.black = StrSave(first.tidy);
12483         gameInfo.timeControl = TimeControlTagValue();
12484         break;
12485
12486       case TwoMachinesPlay:
12487         gameInfo.event = StrSave( appData.pgnEventHeader );
12488         gameInfo.site = StrSave(HostName());
12489         gameInfo.date = PGNDate();
12490         if (matchGame > 0) {
12491             char buf[MSG_SIZ];
12492             sprintf(buf, "%d", matchGame);
12493             gameInfo.round = StrSave(buf);
12494         } else {
12495             gameInfo.round = StrSave("-");
12496         }
12497         if (first.twoMachinesColor[0] == 'w') {
12498             gameInfo.white = StrSave(first.tidy);
12499             gameInfo.black = StrSave(second.tidy);
12500         } else {
12501             gameInfo.white = StrSave(second.tidy);
12502             gameInfo.black = StrSave(first.tidy);
12503         }
12504         gameInfo.timeControl = TimeControlTagValue();
12505         break;
12506
12507       case EditGame:
12508         gameInfo.event = StrSave("Edited game");
12509         gameInfo.site = StrSave(HostName());
12510         gameInfo.date = PGNDate();
12511         gameInfo.round = StrSave("-");
12512         gameInfo.white = StrSave("-");
12513         gameInfo.black = StrSave("-");
12514         gameInfo.result = r;
12515         gameInfo.resultDetails = p;
12516         break;
12517
12518       case EditPosition:
12519         gameInfo.event = StrSave("Edited position");
12520         gameInfo.site = StrSave(HostName());
12521         gameInfo.date = PGNDate();
12522         gameInfo.round = StrSave("-");
12523         gameInfo.white = StrSave("-");
12524         gameInfo.black = StrSave("-");
12525         break;
12526
12527       case IcsPlayingWhite:
12528       case IcsPlayingBlack:
12529       case IcsObserving:
12530       case IcsExamining:
12531         break;
12532
12533       case PlayFromGameFile:
12534         gameInfo.event = StrSave("Game from non-PGN file");
12535         gameInfo.site = StrSave(HostName());
12536         gameInfo.date = PGNDate();
12537         gameInfo.round = StrSave("-");
12538         gameInfo.white = StrSave("?");
12539         gameInfo.black = StrSave("?");
12540         break;
12541
12542       default:
12543         break;
12544     }
12545 }
12546
12547 void
12548 ReplaceComment(index, text)
12549      int index;
12550      char *text;
12551 {
12552     int len;
12553
12554     while (*text == '\n') text++;
12555     len = strlen(text);
12556     while (len > 0 && text[len - 1] == '\n') len--;
12557
12558     if (commentList[index] != NULL)
12559       free(commentList[index]);
12560
12561     if (len == 0) {
12562         commentList[index] = NULL;
12563         return;
12564     }
12565   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12566       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12567       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12568     commentList[index] = (char *) malloc(len + 2);
12569     strncpy(commentList[index], text, len);
12570     commentList[index][len] = '\n';
12571     commentList[index][len + 1] = NULLCHAR;
12572   } else { 
12573     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12574     char *p;
12575     commentList[index] = (char *) malloc(len + 6);
12576     strcpy(commentList[index], "{\n");
12577     strncpy(commentList[index]+2, text, len);
12578     commentList[index][len+2] = NULLCHAR;
12579     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12580     strcat(commentList[index], "\n}\n");
12581   }
12582 }
12583
12584 void
12585 CrushCRs(text)
12586      char *text;
12587 {
12588   char *p = text;
12589   char *q = text;
12590   char ch;
12591
12592   do {
12593     ch = *p++;
12594     if (ch == '\r') continue;
12595     *q++ = ch;
12596   } while (ch != '\0');
12597 }
12598
12599 void
12600 AppendComment(index, text, addBraces)
12601      int index;
12602      char *text;
12603      Boolean addBraces; // [HGM] braces: tells if we should add {}
12604 {
12605     int oldlen, len;
12606     char *old;
12607
12608 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12609     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12610
12611     CrushCRs(text);
12612     while (*text == '\n') text++;
12613     len = strlen(text);
12614     while (len > 0 && text[len - 1] == '\n') len--;
12615
12616     if (len == 0) return;
12617
12618     if (commentList[index] != NULL) {
12619         old = commentList[index];
12620         oldlen = strlen(old);
12621         while(commentList[index][oldlen-1] ==  '\n')
12622           commentList[index][--oldlen] = NULLCHAR;
12623         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12624         strcpy(commentList[index], old);
12625         free(old);
12626         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12627         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12628           if(addBraces) addBraces = FALSE; else { text++; len--; }
12629           while (*text == '\n') { text++; len--; }
12630           commentList[index][--oldlen] = NULLCHAR;
12631       }
12632         if(addBraces) strcat(commentList[index], "\n{\n");
12633         else          strcat(commentList[index], "\n");
12634         strcat(commentList[index], text);
12635         if(addBraces) strcat(commentList[index], "\n}\n");
12636         else          strcat(commentList[index], "\n");
12637     } else {
12638         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12639         if(addBraces)
12640              strcpy(commentList[index], "{\n");
12641         else commentList[index][0] = NULLCHAR;
12642         strcat(commentList[index], text);
12643         strcat(commentList[index], "\n");
12644         if(addBraces) strcat(commentList[index], "}\n");
12645     }
12646 }
12647
12648 static char * FindStr( char * text, char * sub_text )
12649 {
12650     char * result = strstr( text, sub_text );
12651
12652     if( result != NULL ) {
12653         result += strlen( sub_text );
12654     }
12655
12656     return result;
12657 }
12658
12659 /* [AS] Try to extract PV info from PGN comment */
12660 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12661 char *GetInfoFromComment( int index, char * text )
12662 {
12663     char * sep = text;
12664
12665     if( text != NULL && index > 0 ) {
12666         int score = 0;
12667         int depth = 0;
12668         int time = -1, sec = 0, deci;
12669         char * s_eval = FindStr( text, "[%eval " );
12670         char * s_emt = FindStr( text, "[%emt " );
12671
12672         if( s_eval != NULL || s_emt != NULL ) {
12673             /* New style */
12674             char delim;
12675
12676             if( s_eval != NULL ) {
12677                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12678                     return text;
12679                 }
12680
12681                 if( delim != ']' ) {
12682                     return text;
12683                 }
12684             }
12685
12686             if( s_emt != NULL ) {
12687             }
12688                 return text;
12689         }
12690         else {
12691             /* We expect something like: [+|-]nnn.nn/dd */
12692             int score_lo = 0;
12693
12694             if(*text != '{') return text; // [HGM] braces: must be normal comment
12695
12696             sep = strchr( text, '/' );
12697             if( sep == NULL || sep < (text+4) ) {
12698                 return text;
12699             }
12700
12701             time = -1; sec = -1; deci = -1;
12702             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12703                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12704                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12705                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12706                 return text;
12707             }
12708
12709             if( score_lo < 0 || score_lo >= 100 ) {
12710                 return text;
12711             }
12712
12713             if(sec >= 0) time = 600*time + 10*sec; else
12714             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12715
12716             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12717
12718             /* [HGM] PV time: now locate end of PV info */
12719             while( *++sep >= '0' && *sep <= '9'); // strip depth
12720             if(time >= 0)
12721             while( *++sep >= '0' && *sep <= '9'); // strip time
12722             if(sec >= 0)
12723             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12724             if(deci >= 0)
12725             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12726             while(*sep == ' ') sep++;
12727         }
12728
12729         if( depth <= 0 ) {
12730             return text;
12731         }
12732
12733         if( time < 0 ) {
12734             time = -1;
12735         }
12736
12737         pvInfoList[index-1].depth = depth;
12738         pvInfoList[index-1].score = score;
12739         pvInfoList[index-1].time  = 10*time; // centi-sec
12740         if(*sep == '}') *sep = 0; else *--sep = '{';
12741     }
12742     return sep;
12743 }
12744
12745 void
12746 SendToProgram(message, cps)
12747      char *message;
12748      ChessProgramState *cps;
12749 {
12750     int count, outCount, error;
12751     char buf[MSG_SIZ];
12752
12753     if (cps->pr == NULL) return;
12754     Attention(cps);
12755     
12756     if (appData.debugMode) {
12757         TimeMark now;
12758         GetTimeMark(&now);
12759         fprintf(debugFP, "%ld >%-6s: %s", 
12760                 SubtractTimeMarks(&now, &programStartTime),
12761                 cps->which, message);
12762     }
12763     
12764     count = strlen(message);
12765     outCount = OutputToProcess(cps->pr, message, count, &error);
12766     if (outCount < count && !exiting 
12767                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12768         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12769         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12770             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12771                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12772                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12773             } else {
12774                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12775             }
12776             gameInfo.resultDetails = StrSave(buf);
12777         }
12778         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12779     }
12780 }
12781
12782 void
12783 ReceiveFromProgram(isr, closure, message, count, error)
12784      InputSourceRef isr;
12785      VOIDSTAR closure;
12786      char *message;
12787      int count;
12788      int error;
12789 {
12790     char *end_str;
12791     char buf[MSG_SIZ];
12792     ChessProgramState *cps = (ChessProgramState *)closure;
12793
12794     if (isr != cps->isr) return; /* Killed intentionally */
12795     if (count <= 0) {
12796         if (count == 0) {
12797             sprintf(buf,
12798                     _("Error: %s chess program (%s) exited unexpectedly"),
12799                     cps->which, cps->program);
12800         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12801                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12802                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12803                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12804                 } else {
12805                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12806                 }
12807                 gameInfo.resultDetails = StrSave(buf);
12808             }
12809             RemoveInputSource(cps->isr);
12810             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12811         } else {
12812             sprintf(buf,
12813                     _("Error reading from %s chess program (%s)"),
12814                     cps->which, cps->program);
12815             RemoveInputSource(cps->isr);
12816
12817             /* [AS] Program is misbehaving badly... kill it */
12818             if( count == -2 ) {
12819                 DestroyChildProcess( cps->pr, 9 );
12820                 cps->pr = NoProc;
12821             }
12822
12823             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12824         }
12825         return;
12826     }
12827     
12828     if ((end_str = strchr(message, '\r')) != NULL)
12829       *end_str = NULLCHAR;
12830     if ((end_str = strchr(message, '\n')) != NULL)
12831       *end_str = NULLCHAR;
12832     
12833     if (appData.debugMode) {
12834         TimeMark now; int print = 1;
12835         char *quote = ""; char c; int i;
12836
12837         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12838                 char start = message[0];
12839                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12840                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12841                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12842                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12843                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12844                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12845                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12846                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12847                         { quote = "# "; print = (appData.engineComments == 2); }
12848                 message[0] = start; // restore original message
12849         }
12850         if(print) {
12851                 GetTimeMark(&now);
12852                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12853                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12854                         quote,
12855                         message);
12856         }
12857     }
12858
12859     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12860     if (appData.icsEngineAnalyze) {
12861         if (strstr(message, "whisper") != NULL ||
12862              strstr(message, "kibitz") != NULL || 
12863             strstr(message, "tellics") != NULL) return;
12864     }
12865
12866     HandleMachineMove(message, cps);
12867 }
12868
12869
12870 void
12871 SendTimeControl(cps, mps, tc, inc, sd, st)
12872      ChessProgramState *cps;
12873      int mps, inc, sd, st;
12874      long tc;
12875 {
12876     char buf[MSG_SIZ];
12877     int seconds;
12878
12879     if( timeControl_2 > 0 ) {
12880         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12881             tc = timeControl_2;
12882         }
12883     }
12884     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12885     inc /= cps->timeOdds;
12886     st  /= cps->timeOdds;
12887
12888     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12889
12890     if (st > 0) {
12891       /* Set exact time per move, normally using st command */
12892       if (cps->stKludge) {
12893         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12894         seconds = st % 60;
12895         if (seconds == 0) {
12896           sprintf(buf, "level 1 %d\n", st/60);
12897         } else {
12898           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12899         }
12900       } else {
12901         sprintf(buf, "st %d\n", st);
12902       }
12903     } else {
12904       /* Set conventional or incremental time control, using level command */
12905       if (seconds == 0) {
12906         /* Note old gnuchess bug -- minutes:seconds used to not work.
12907            Fixed in later versions, but still avoid :seconds
12908            when seconds is 0. */
12909         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12910       } else {
12911         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12912                 seconds, inc/1000);
12913       }
12914     }
12915     SendToProgram(buf, cps);
12916
12917     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12918     /* Orthogonally, limit search to given depth */
12919     if (sd > 0) {
12920       if (cps->sdKludge) {
12921         sprintf(buf, "depth\n%d\n", sd);
12922       } else {
12923         sprintf(buf, "sd %d\n", sd);
12924       }
12925       SendToProgram(buf, cps);
12926     }
12927
12928     if(cps->nps > 0) { /* [HGM] nps */
12929         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12930         else {
12931                 sprintf(buf, "nps %d\n", cps->nps);
12932               SendToProgram(buf, cps);
12933         }
12934     }
12935 }
12936
12937 ChessProgramState *WhitePlayer()
12938 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12939 {
12940     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12941        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12942         return &second;
12943     return &first;
12944 }
12945
12946 void
12947 SendTimeRemaining(cps, machineWhite)
12948      ChessProgramState *cps;
12949      int /*boolean*/ machineWhite;
12950 {
12951     char message[MSG_SIZ];
12952     long time, otime;
12953
12954     /* Note: this routine must be called when the clocks are stopped
12955        or when they have *just* been set or switched; otherwise
12956        it will be off by the time since the current tick started.
12957     */
12958     if (machineWhite) {
12959         time = whiteTimeRemaining / 10;
12960         otime = blackTimeRemaining / 10;
12961     } else {
12962         time = blackTimeRemaining / 10;
12963         otime = whiteTimeRemaining / 10;
12964     }
12965     /* [HGM] translate opponent's time by time-odds factor */
12966     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12967     if (appData.debugMode) {
12968         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12969     }
12970
12971     if (time <= 0) time = 1;
12972     if (otime <= 0) otime = 1;
12973     
12974     sprintf(message, "time %ld\n", time);
12975     SendToProgram(message, cps);
12976
12977     sprintf(message, "otim %ld\n", otime);
12978     SendToProgram(message, cps);
12979 }
12980
12981 int
12982 BoolFeature(p, name, loc, cps)
12983      char **p;
12984      char *name;
12985      int *loc;
12986      ChessProgramState *cps;
12987 {
12988   char buf[MSG_SIZ];
12989   int len = strlen(name);
12990   int val;
12991   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12992     (*p) += len + 1;
12993     sscanf(*p, "%d", &val);
12994     *loc = (val != 0);
12995     while (**p && **p != ' ') (*p)++;
12996     sprintf(buf, "accepted %s\n", name);
12997     SendToProgram(buf, cps);
12998     return TRUE;
12999   }
13000   return FALSE;
13001 }
13002
13003 int
13004 IntFeature(p, name, loc, cps)
13005      char **p;
13006      char *name;
13007      int *loc;
13008      ChessProgramState *cps;
13009 {
13010   char buf[MSG_SIZ];
13011   int len = strlen(name);
13012   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13013     (*p) += len + 1;
13014     sscanf(*p, "%d", loc);
13015     while (**p && **p != ' ') (*p)++;
13016     sprintf(buf, "accepted %s\n", name);
13017     SendToProgram(buf, cps);
13018     return TRUE;
13019   }
13020   return FALSE;
13021 }
13022
13023 int
13024 StringFeature(p, name, loc, cps)
13025      char **p;
13026      char *name;
13027      char loc[];
13028      ChessProgramState *cps;
13029 {
13030   char buf[MSG_SIZ];
13031   int len = strlen(name);
13032   if (strncmp((*p), name, len) == 0
13033       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13034     (*p) += len + 2;
13035     sscanf(*p, "%[^\"]", loc);
13036     while (**p && **p != '\"') (*p)++;
13037     if (**p == '\"') (*p)++;
13038     sprintf(buf, "accepted %s\n", name);
13039     SendToProgram(buf, cps);
13040     return TRUE;
13041   }
13042   return FALSE;
13043 }
13044
13045 int 
13046 ParseOption(Option *opt, ChessProgramState *cps)
13047 // [HGM] options: process the string that defines an engine option, and determine
13048 // name, type, default value, and allowed value range
13049 {
13050         char *p, *q, buf[MSG_SIZ];
13051         int n, min = (-1)<<31, max = 1<<31, def;
13052
13053         if(p = strstr(opt->name, " -spin ")) {
13054             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13055             if(max < min) max = min; // enforce consistency
13056             if(def < min) def = min;
13057             if(def > max) def = max;
13058             opt->value = def;
13059             opt->min = min;
13060             opt->max = max;
13061             opt->type = Spin;
13062         } else if((p = strstr(opt->name, " -slider "))) {
13063             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13064             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13065             if(max < min) max = min; // enforce consistency
13066             if(def < min) def = min;
13067             if(def > max) def = max;
13068             opt->value = def;
13069             opt->min = min;
13070             opt->max = max;
13071             opt->type = Spin; // Slider;
13072         } else if((p = strstr(opt->name, " -string "))) {
13073             opt->textValue = p+9;
13074             opt->type = TextBox;
13075         } else if((p = strstr(opt->name, " -file "))) {
13076             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13077             opt->textValue = p+7;
13078             opt->type = TextBox; // FileName;
13079         } else if((p = strstr(opt->name, " -path "))) {
13080             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13081             opt->textValue = p+7;
13082             opt->type = TextBox; // PathName;
13083         } else if(p = strstr(opt->name, " -check ")) {
13084             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13085             opt->value = (def != 0);
13086             opt->type = CheckBox;
13087         } else if(p = strstr(opt->name, " -combo ")) {
13088             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13089             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13090             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13091             opt->value = n = 0;
13092             while(q = StrStr(q, " /// ")) {
13093                 n++; *q = 0;    // count choices, and null-terminate each of them
13094                 q += 5;
13095                 if(*q == '*') { // remember default, which is marked with * prefix
13096                     q++;
13097                     opt->value = n;
13098                 }
13099                 cps->comboList[cps->comboCnt++] = q;
13100             }
13101             cps->comboList[cps->comboCnt++] = NULL;
13102             opt->max = n + 1;
13103             opt->type = ComboBox;
13104         } else if(p = strstr(opt->name, " -button")) {
13105             opt->type = Button;
13106         } else if(p = strstr(opt->name, " -save")) {
13107             opt->type = SaveButton;
13108         } else return FALSE;
13109         *p = 0; // terminate option name
13110         // now look if the command-line options define a setting for this engine option.
13111         if(cps->optionSettings && cps->optionSettings[0])
13112             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13113         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13114                 sprintf(buf, "option %s", p);
13115                 if(p = strstr(buf, ",")) *p = 0;
13116                 strcat(buf, "\n");
13117                 SendToProgram(buf, cps);
13118         }
13119         return TRUE;
13120 }
13121
13122 void
13123 FeatureDone(cps, val)
13124      ChessProgramState* cps;
13125      int val;
13126 {
13127   DelayedEventCallback cb = GetDelayedEvent();
13128   if ((cb == InitBackEnd3 && cps == &first) ||
13129       (cb == TwoMachinesEventIfReady && cps == &second)) {
13130     CancelDelayedEvent();
13131     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13132   }
13133   cps->initDone = val;
13134 }
13135
13136 /* Parse feature command from engine */
13137 void
13138 ParseFeatures(args, cps)
13139      char* args;
13140      ChessProgramState *cps;  
13141 {
13142   char *p = args;
13143   char *q;
13144   int val;
13145   char buf[MSG_SIZ];
13146
13147   for (;;) {
13148     while (*p == ' ') p++;
13149     if (*p == NULLCHAR) return;
13150
13151     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13152     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13153     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13154     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13155     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13156     if (BoolFeature(&p, "reuse", &val, cps)) {
13157       /* Engine can disable reuse, but can't enable it if user said no */
13158       if (!val) cps->reuse = FALSE;
13159       continue;
13160     }
13161     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13162     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13163       if (gameMode == TwoMachinesPlay) {
13164         DisplayTwoMachinesTitle();
13165       } else {
13166         DisplayTitle("");
13167       }
13168       continue;
13169     }
13170     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13171     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13172     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13173     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13174     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13175     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13176     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13177     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13178     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13179     if (IntFeature(&p, "done", &val, cps)) {
13180       FeatureDone(cps, val);
13181       continue;
13182     }
13183     /* Added by Tord: */
13184     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13185     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13186     /* End of additions by Tord */
13187
13188     /* [HGM] added features: */
13189     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13190     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13191     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13192     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13193     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13194     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13195     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13196         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13197             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13198             SendToProgram(buf, cps);
13199             continue;
13200         }
13201         if(cps->nrOptions >= MAX_OPTIONS) {
13202             cps->nrOptions--;
13203             sprintf(buf, "%s engine has too many options\n", cps->which);
13204             DisplayError(buf, 0);
13205         }
13206         continue;
13207     }
13208     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13209     /* End of additions by HGM */
13210
13211     /* unknown feature: complain and skip */
13212     q = p;
13213     while (*q && *q != '=') q++;
13214     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13215     SendToProgram(buf, cps);
13216     p = q;
13217     if (*p == '=') {
13218       p++;
13219       if (*p == '\"') {
13220         p++;
13221         while (*p && *p != '\"') p++;
13222         if (*p == '\"') p++;
13223       } else {
13224         while (*p && *p != ' ') p++;
13225       }
13226     }
13227   }
13228
13229 }
13230
13231 void
13232 PeriodicUpdatesEvent(newState)
13233      int newState;
13234 {
13235     if (newState == appData.periodicUpdates)
13236       return;
13237
13238     appData.periodicUpdates=newState;
13239
13240     /* Display type changes, so update it now */
13241 //    DisplayAnalysis();
13242
13243     /* Get the ball rolling again... */
13244     if (newState) {
13245         AnalysisPeriodicEvent(1);
13246         StartAnalysisClock();
13247     }
13248 }
13249
13250 void
13251 PonderNextMoveEvent(newState)
13252      int newState;
13253 {
13254     if (newState == appData.ponderNextMove) return;
13255     if (gameMode == EditPosition) EditPositionDone(TRUE);
13256     if (newState) {
13257         SendToProgram("hard\n", &first);
13258         if (gameMode == TwoMachinesPlay) {
13259             SendToProgram("hard\n", &second);
13260         }
13261     } else {
13262         SendToProgram("easy\n", &first);
13263         thinkOutput[0] = NULLCHAR;
13264         if (gameMode == TwoMachinesPlay) {
13265             SendToProgram("easy\n", &second);
13266         }
13267     }
13268     appData.ponderNextMove = newState;
13269 }
13270
13271 void
13272 NewSettingEvent(option, command, value)
13273      char *command;
13274      int option, value;
13275 {
13276     char buf[MSG_SIZ];
13277
13278     if (gameMode == EditPosition) EditPositionDone(TRUE);
13279     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13280     SendToProgram(buf, &first);
13281     if (gameMode == TwoMachinesPlay) {
13282         SendToProgram(buf, &second);
13283     }
13284 }
13285
13286 void
13287 ShowThinkingEvent()
13288 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13289 {
13290     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13291     int newState = appData.showThinking
13292         // [HGM] thinking: other features now need thinking output as well
13293         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13294     
13295     if (oldState == newState) return;
13296     oldState = newState;
13297     if (gameMode == EditPosition) EditPositionDone(TRUE);
13298     if (oldState) {
13299         SendToProgram("post\n", &first);
13300         if (gameMode == TwoMachinesPlay) {
13301             SendToProgram("post\n", &second);
13302         }
13303     } else {
13304         SendToProgram("nopost\n", &first);
13305         thinkOutput[0] = NULLCHAR;
13306         if (gameMode == TwoMachinesPlay) {
13307             SendToProgram("nopost\n", &second);
13308         }
13309     }
13310 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13311 }
13312
13313 void
13314 AskQuestionEvent(title, question, replyPrefix, which)
13315      char *title; char *question; char *replyPrefix; char *which;
13316 {
13317   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13318   if (pr == NoProc) return;
13319   AskQuestion(title, question, replyPrefix, pr);
13320 }
13321
13322 void
13323 DisplayMove(moveNumber)
13324      int moveNumber;
13325 {
13326     char message[MSG_SIZ];
13327     char res[MSG_SIZ];
13328     char cpThinkOutput[MSG_SIZ];
13329
13330     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13331     
13332     if (moveNumber == forwardMostMove - 1 || 
13333         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13334
13335         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13336
13337         if (strchr(cpThinkOutput, '\n')) {
13338             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13339         }
13340     } else {
13341         *cpThinkOutput = NULLCHAR;
13342     }
13343
13344     /* [AS] Hide thinking from human user */
13345     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13346         *cpThinkOutput = NULLCHAR;
13347         if( thinkOutput[0] != NULLCHAR ) {
13348             int i;
13349
13350             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13351                 cpThinkOutput[i] = '.';
13352             }
13353             cpThinkOutput[i] = NULLCHAR;
13354             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13355         }
13356     }
13357
13358     if (moveNumber == forwardMostMove - 1 &&
13359         gameInfo.resultDetails != NULL) {
13360         if (gameInfo.resultDetails[0] == NULLCHAR) {
13361             sprintf(res, " %s", PGNResult(gameInfo.result));
13362         } else {
13363             sprintf(res, " {%s} %s",
13364                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13365         }
13366     } else {
13367         res[0] = NULLCHAR;
13368     }
13369
13370     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13371         DisplayMessage(res, cpThinkOutput);
13372     } else {
13373         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13374                 WhiteOnMove(moveNumber) ? " " : ".. ",
13375                 parseList[moveNumber], res);
13376         DisplayMessage(message, cpThinkOutput);
13377     }
13378 }
13379
13380 void
13381 DisplayComment(moveNumber, text)
13382      int moveNumber;
13383      char *text;
13384 {
13385     char title[MSG_SIZ];
13386     char buf[8000]; // comment can be long!
13387     int score, depth;
13388     
13389     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13390       strcpy(title, "Comment");
13391     } else {
13392       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13393               WhiteOnMove(moveNumber) ? " " : ".. ",
13394               parseList[moveNumber]);
13395     }
13396     // [HGM] PV info: display PV info together with (or as) comment
13397     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13398       if(text == NULL) text = "";                                           
13399       score = pvInfoList[moveNumber].score;
13400       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13401               depth, (pvInfoList[moveNumber].time+50)/100, text);
13402       text = buf;
13403     }
13404     if (text != NULL && (appData.autoDisplayComment || commentUp))
13405         CommentPopUp(title, text);
13406 }
13407
13408 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13409  * might be busy thinking or pondering.  It can be omitted if your
13410  * gnuchess is configured to stop thinking immediately on any user
13411  * input.  However, that gnuchess feature depends on the FIONREAD
13412  * ioctl, which does not work properly on some flavors of Unix.
13413  */
13414 void
13415 Attention(cps)
13416      ChessProgramState *cps;
13417 {
13418 #if ATTENTION
13419     if (!cps->useSigint) return;
13420     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13421     switch (gameMode) {
13422       case MachinePlaysWhite:
13423       case MachinePlaysBlack:
13424       case TwoMachinesPlay:
13425       case IcsPlayingWhite:
13426       case IcsPlayingBlack:
13427       case AnalyzeMode:
13428       case AnalyzeFile:
13429         /* Skip if we know it isn't thinking */
13430         if (!cps->maybeThinking) return;
13431         if (appData.debugMode)
13432           fprintf(debugFP, "Interrupting %s\n", cps->which);
13433         InterruptChildProcess(cps->pr);
13434         cps->maybeThinking = FALSE;
13435         break;
13436       default:
13437         break;
13438     }
13439 #endif /*ATTENTION*/
13440 }
13441
13442 int
13443 CheckFlags()
13444 {
13445     if (whiteTimeRemaining <= 0) {
13446         if (!whiteFlag) {
13447             whiteFlag = TRUE;
13448             if (appData.icsActive) {
13449                 if (appData.autoCallFlag &&
13450                     gameMode == IcsPlayingBlack && !blackFlag) {
13451                   SendToICS(ics_prefix);
13452                   SendToICS("flag\n");
13453                 }
13454             } else {
13455                 if (blackFlag) {
13456                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13457                 } else {
13458                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13459                     if (appData.autoCallFlag) {
13460                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13461                         return TRUE;
13462                     }
13463                 }
13464             }
13465         }
13466     }
13467     if (blackTimeRemaining <= 0) {
13468         if (!blackFlag) {
13469             blackFlag = TRUE;
13470             if (appData.icsActive) {
13471                 if (appData.autoCallFlag &&
13472                     gameMode == IcsPlayingWhite && !whiteFlag) {
13473                   SendToICS(ics_prefix);
13474                   SendToICS("flag\n");
13475                 }
13476             } else {
13477                 if (whiteFlag) {
13478                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13479                 } else {
13480                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13481                     if (appData.autoCallFlag) {
13482                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13483                         return TRUE;
13484                     }
13485                 }
13486             }
13487         }
13488     }
13489     return FALSE;
13490 }
13491
13492 void
13493 CheckTimeControl()
13494 {
13495     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13496         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13497
13498     /*
13499      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13500      */
13501     if ( !WhiteOnMove(forwardMostMove) )
13502         /* White made time control */
13503         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13504         /* [HGM] time odds: correct new time quota for time odds! */
13505                                             / WhitePlayer()->timeOdds;
13506       else
13507         /* Black made time control */
13508         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13509                                             / WhitePlayer()->other->timeOdds;
13510 }
13511
13512 void
13513 DisplayBothClocks()
13514 {
13515     int wom = gameMode == EditPosition ?
13516       !blackPlaysFirst : WhiteOnMove(currentMove);
13517     DisplayWhiteClock(whiteTimeRemaining, wom);
13518     DisplayBlackClock(blackTimeRemaining, !wom);
13519 }
13520
13521
13522 /* Timekeeping seems to be a portability nightmare.  I think everyone
13523    has ftime(), but I'm really not sure, so I'm including some ifdefs
13524    to use other calls if you don't.  Clocks will be less accurate if
13525    you have neither ftime nor gettimeofday.
13526 */
13527
13528 /* VS 2008 requires the #include outside of the function */
13529 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13530 #include <sys/timeb.h>
13531 #endif
13532
13533 /* Get the current time as a TimeMark */
13534 void
13535 GetTimeMark(tm)
13536      TimeMark *tm;
13537 {
13538 #if HAVE_GETTIMEOFDAY
13539
13540     struct timeval timeVal;
13541     struct timezone timeZone;
13542
13543     gettimeofday(&timeVal, &timeZone);
13544     tm->sec = (long) timeVal.tv_sec; 
13545     tm->ms = (int) (timeVal.tv_usec / 1000L);
13546
13547 #else /*!HAVE_GETTIMEOFDAY*/
13548 #if HAVE_FTIME
13549
13550 // include <sys/timeb.h> / moved to just above start of function
13551     struct timeb timeB;
13552
13553     ftime(&timeB);
13554     tm->sec = (long) timeB.time;
13555     tm->ms = (int) timeB.millitm;
13556
13557 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13558     tm->sec = (long) time(NULL);
13559     tm->ms = 0;
13560 #endif
13561 #endif
13562 }
13563
13564 /* Return the difference in milliseconds between two
13565    time marks.  We assume the difference will fit in a long!
13566 */
13567 long
13568 SubtractTimeMarks(tm2, tm1)
13569      TimeMark *tm2, *tm1;
13570 {
13571     return 1000L*(tm2->sec - tm1->sec) +
13572            (long) (tm2->ms - tm1->ms);
13573 }
13574
13575
13576 /*
13577  * Code to manage the game clocks.
13578  *
13579  * In tournament play, black starts the clock and then white makes a move.
13580  * We give the human user a slight advantage if he is playing white---the
13581  * clocks don't run until he makes his first move, so it takes zero time.
13582  * Also, we don't account for network lag, so we could get out of sync
13583  * with GNU Chess's clock -- but then, referees are always right.  
13584  */
13585
13586 static TimeMark tickStartTM;
13587 static long intendedTickLength;
13588
13589 long
13590 NextTickLength(timeRemaining)
13591      long timeRemaining;
13592 {
13593     long nominalTickLength, nextTickLength;
13594
13595     if (timeRemaining > 0L && timeRemaining <= 10000L)
13596       nominalTickLength = 100L;
13597     else
13598       nominalTickLength = 1000L;
13599     nextTickLength = timeRemaining % nominalTickLength;
13600     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13601
13602     return nextTickLength;
13603 }
13604
13605 /* Adjust clock one minute up or down */
13606 void
13607 AdjustClock(Boolean which, int dir)
13608 {
13609     if(which) blackTimeRemaining += 60000*dir;
13610     else      whiteTimeRemaining += 60000*dir;
13611     DisplayBothClocks();
13612 }
13613
13614 /* Stop clocks and reset to a fresh time control */
13615 void
13616 ResetClocks() 
13617 {
13618     (void) StopClockTimer();
13619     if (appData.icsActive) {
13620         whiteTimeRemaining = blackTimeRemaining = 0;
13621     } else if (searchTime) {
13622         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13623         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13624     } else { /* [HGM] correct new time quote for time odds */
13625         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13626         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13627     }
13628     if (whiteFlag || blackFlag) {
13629         DisplayTitle("");
13630         whiteFlag = blackFlag = FALSE;
13631     }
13632     DisplayBothClocks();
13633 }
13634
13635 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13636
13637 /* Decrement running clock by amount of time that has passed */
13638 void
13639 DecrementClocks()
13640 {
13641     long timeRemaining;
13642     long lastTickLength, fudge;
13643     TimeMark now;
13644
13645     if (!appData.clockMode) return;
13646     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13647         
13648     GetTimeMark(&now);
13649
13650     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13651
13652     /* Fudge if we woke up a little too soon */
13653     fudge = intendedTickLength - lastTickLength;
13654     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13655
13656     if (WhiteOnMove(forwardMostMove)) {
13657         if(whiteNPS >= 0) lastTickLength = 0;
13658         timeRemaining = whiteTimeRemaining -= lastTickLength;
13659         DisplayWhiteClock(whiteTimeRemaining - fudge,
13660                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13661     } else {
13662         if(blackNPS >= 0) lastTickLength = 0;
13663         timeRemaining = blackTimeRemaining -= lastTickLength;
13664         DisplayBlackClock(blackTimeRemaining - fudge,
13665                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13666     }
13667
13668     if (CheckFlags()) return;
13669         
13670     tickStartTM = now;
13671     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13672     StartClockTimer(intendedTickLength);
13673
13674     /* if the time remaining has fallen below the alarm threshold, sound the
13675      * alarm. if the alarm has sounded and (due to a takeback or time control
13676      * with increment) the time remaining has increased to a level above the
13677      * threshold, reset the alarm so it can sound again. 
13678      */
13679     
13680     if (appData.icsActive && appData.icsAlarm) {
13681
13682         /* make sure we are dealing with the user's clock */
13683         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13684                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13685            )) return;
13686
13687         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13688             alarmSounded = FALSE;
13689         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13690             PlayAlarmSound();
13691             alarmSounded = TRUE;
13692         }
13693     }
13694 }
13695
13696
13697 /* A player has just moved, so stop the previously running
13698    clock and (if in clock mode) start the other one.
13699    We redisplay both clocks in case we're in ICS mode, because
13700    ICS gives us an update to both clocks after every move.
13701    Note that this routine is called *after* forwardMostMove
13702    is updated, so the last fractional tick must be subtracted
13703    from the color that is *not* on move now.
13704 */
13705 void
13706 SwitchClocks()
13707 {
13708     long lastTickLength;
13709     TimeMark now;
13710     int flagged = FALSE;
13711
13712     GetTimeMark(&now);
13713
13714     if (StopClockTimer() && appData.clockMode) {
13715         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13716         if (WhiteOnMove(forwardMostMove)) {
13717             if(blackNPS >= 0) lastTickLength = 0;
13718             blackTimeRemaining -= lastTickLength;
13719            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13720 //         if(pvInfoList[forwardMostMove-1].time == -1)
13721                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13722                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13723         } else {
13724            if(whiteNPS >= 0) lastTickLength = 0;
13725            whiteTimeRemaining -= lastTickLength;
13726            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13727 //         if(pvInfoList[forwardMostMove-1].time == -1)
13728                  pvInfoList[forwardMostMove-1].time = 
13729                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13730         }
13731         flagged = CheckFlags();
13732     }
13733     CheckTimeControl();
13734
13735     if (flagged || !appData.clockMode) return;
13736
13737     switch (gameMode) {
13738       case MachinePlaysBlack:
13739       case MachinePlaysWhite:
13740       case BeginningOfGame:
13741         if (pausing) return;
13742         break;
13743
13744       case EditGame:
13745       case PlayFromGameFile:
13746       case IcsExamining:
13747         return;
13748
13749       default:
13750         break;
13751     }
13752
13753     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13754         if(WhiteOnMove(forwardMostMove))
13755              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13756         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13757     }
13758
13759     tickStartTM = now;
13760     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13761       whiteTimeRemaining : blackTimeRemaining);
13762     StartClockTimer(intendedTickLength);
13763 }
13764         
13765
13766 /* Stop both clocks */
13767 void
13768 StopClocks()
13769 {       
13770     long lastTickLength;
13771     TimeMark now;
13772
13773     if (!StopClockTimer()) return;
13774     if (!appData.clockMode) return;
13775
13776     GetTimeMark(&now);
13777
13778     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13779     if (WhiteOnMove(forwardMostMove)) {
13780         if(whiteNPS >= 0) lastTickLength = 0;
13781         whiteTimeRemaining -= lastTickLength;
13782         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13783     } else {
13784         if(blackNPS >= 0) lastTickLength = 0;
13785         blackTimeRemaining -= lastTickLength;
13786         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13787     }
13788     CheckFlags();
13789 }
13790         
13791 /* Start clock of player on move.  Time may have been reset, so
13792    if clock is already running, stop and restart it. */
13793 void
13794 StartClocks()
13795 {
13796     (void) StopClockTimer(); /* in case it was running already */
13797     DisplayBothClocks();
13798     if (CheckFlags()) return;
13799
13800     if (!appData.clockMode) return;
13801     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13802
13803     GetTimeMark(&tickStartTM);
13804     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13805       whiteTimeRemaining : blackTimeRemaining);
13806
13807    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13808     whiteNPS = blackNPS = -1; 
13809     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13810        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13811         whiteNPS = first.nps;
13812     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13813        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13814         blackNPS = first.nps;
13815     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13816         whiteNPS = second.nps;
13817     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13818         blackNPS = second.nps;
13819     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13820
13821     StartClockTimer(intendedTickLength);
13822 }
13823
13824 char *
13825 TimeString(ms)
13826      long ms;
13827 {
13828     long second, minute, hour, day;
13829     char *sign = "";
13830     static char buf[32];
13831     
13832     if (ms > 0 && ms <= 9900) {
13833       /* convert milliseconds to tenths, rounding up */
13834       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13835
13836       sprintf(buf, " %03.1f ", tenths/10.0);
13837       return buf;
13838     }
13839
13840     /* convert milliseconds to seconds, rounding up */
13841     /* use floating point to avoid strangeness of integer division
13842        with negative dividends on many machines */
13843     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13844
13845     if (second < 0) {
13846         sign = "-";
13847         second = -second;
13848     }
13849     
13850     day = second / (60 * 60 * 24);
13851     second = second % (60 * 60 * 24);
13852     hour = second / (60 * 60);
13853     second = second % (60 * 60);
13854     minute = second / 60;
13855     second = second % 60;
13856     
13857     if (day > 0)
13858       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13859               sign, day, hour, minute, second);
13860     else if (hour > 0)
13861       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13862     else
13863       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13864     
13865     return buf;
13866 }
13867
13868
13869 /*
13870  * This is necessary because some C libraries aren't ANSI C compliant yet.
13871  */
13872 char *
13873 StrStr(string, match)
13874      char *string, *match;
13875 {
13876     int i, length;
13877     
13878     length = strlen(match);
13879     
13880     for (i = strlen(string) - length; i >= 0; i--, string++)
13881       if (!strncmp(match, string, length))
13882         return string;
13883     
13884     return NULL;
13885 }
13886
13887 char *
13888 StrCaseStr(string, match)
13889      char *string, *match;
13890 {
13891     int i, j, length;
13892     
13893     length = strlen(match);
13894     
13895     for (i = strlen(string) - length; i >= 0; i--, string++) {
13896         for (j = 0; j < length; j++) {
13897             if (ToLower(match[j]) != ToLower(string[j]))
13898               break;
13899         }
13900         if (j == length) return string;
13901     }
13902
13903     return NULL;
13904 }
13905
13906 #ifndef _amigados
13907 int
13908 StrCaseCmp(s1, s2)
13909      char *s1, *s2;
13910 {
13911     char c1, c2;
13912     
13913     for (;;) {
13914         c1 = ToLower(*s1++);
13915         c2 = ToLower(*s2++);
13916         if (c1 > c2) return 1;
13917         if (c1 < c2) return -1;
13918         if (c1 == NULLCHAR) return 0;
13919     }
13920 }
13921
13922
13923 int
13924 ToLower(c)
13925      int c;
13926 {
13927     return isupper(c) ? tolower(c) : c;
13928 }
13929
13930
13931 int
13932 ToUpper(c)
13933      int c;
13934 {
13935     return islower(c) ? toupper(c) : c;
13936 }
13937 #endif /* !_amigados    */
13938
13939 char *
13940 StrSave(s)
13941      char *s;
13942 {
13943     char *ret;
13944
13945     if ((ret = (char *) malloc(strlen(s) + 1))) {
13946         strcpy(ret, s);
13947     }
13948     return ret;
13949 }
13950
13951 char *
13952 StrSavePtr(s, savePtr)
13953      char *s, **savePtr;
13954 {
13955     if (*savePtr) {
13956         free(*savePtr);
13957     }
13958     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13959         strcpy(*savePtr, s);
13960     }
13961     return(*savePtr);
13962 }
13963
13964 char *
13965 PGNDate()
13966 {
13967     time_t clock;
13968     struct tm *tm;
13969     char buf[MSG_SIZ];
13970
13971     clock = time((time_t *)NULL);
13972     tm = localtime(&clock);
13973     sprintf(buf, "%04d.%02d.%02d",
13974             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13975     return StrSave(buf);
13976 }
13977
13978
13979 char *
13980 PositionToFEN(move, overrideCastling)
13981      int move;
13982      char *overrideCastling;
13983 {
13984     int i, j, fromX, fromY, toX, toY;
13985     int whiteToPlay;
13986     char buf[128];
13987     char *p, *q;
13988     int emptycount;
13989     ChessSquare piece;
13990
13991     whiteToPlay = (gameMode == EditPosition) ?
13992       !blackPlaysFirst : (move % 2 == 0);
13993     p = buf;
13994
13995     /* Piece placement data */
13996     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13997         emptycount = 0;
13998         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13999             if (boards[move][i][j] == EmptySquare) {
14000                 emptycount++;
14001             } else { ChessSquare piece = boards[move][i][j];
14002                 if (emptycount > 0) {
14003                     if(emptycount<10) /* [HGM] can be >= 10 */
14004                         *p++ = '0' + emptycount;
14005                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14006                     emptycount = 0;
14007                 }
14008                 if(PieceToChar(piece) == '+') {
14009                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14010                     *p++ = '+';
14011                     piece = (ChessSquare)(DEMOTED piece);
14012                 } 
14013                 *p++ = PieceToChar(piece);
14014                 if(p[-1] == '~') {
14015                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14016                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14017                     *p++ = '~';
14018                 }
14019             }
14020         }
14021         if (emptycount > 0) {
14022             if(emptycount<10) /* [HGM] can be >= 10 */
14023                 *p++ = '0' + emptycount;
14024             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14025             emptycount = 0;
14026         }
14027         *p++ = '/';
14028     }
14029     *(p - 1) = ' ';
14030
14031     /* [HGM] print Crazyhouse or Shogi holdings */
14032     if( gameInfo.holdingsWidth ) {
14033         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14034         q = p;
14035         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14036             piece = boards[move][i][BOARD_WIDTH-1];
14037             if( piece != EmptySquare )
14038               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14039                   *p++ = PieceToChar(piece);
14040         }
14041         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14042             piece = boards[move][BOARD_HEIGHT-i-1][0];
14043             if( piece != EmptySquare )
14044               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14045                   *p++ = PieceToChar(piece);
14046         }
14047
14048         if( q == p ) *p++ = '-';
14049         *p++ = ']';
14050         *p++ = ' ';
14051     }
14052
14053     /* Active color */
14054     *p++ = whiteToPlay ? 'w' : 'b';
14055     *p++ = ' ';
14056
14057   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14058     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14059   } else {
14060   if(nrCastlingRights) {
14061      q = p;
14062      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14063        /* [HGM] write directly from rights */
14064            if(boards[move][CASTLING][2] != NoRights &&
14065               boards[move][CASTLING][0] != NoRights   )
14066                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14067            if(boards[move][CASTLING][2] != NoRights &&
14068               boards[move][CASTLING][1] != NoRights   )
14069                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14070            if(boards[move][CASTLING][5] != NoRights &&
14071               boards[move][CASTLING][3] != NoRights   )
14072                 *p++ = boards[move][CASTLING][3] + AAA;
14073            if(boards[move][CASTLING][5] != NoRights &&
14074               boards[move][CASTLING][4] != NoRights   )
14075                 *p++ = boards[move][CASTLING][4] + AAA;
14076      } else {
14077
14078         /* [HGM] write true castling rights */
14079         if( nrCastlingRights == 6 ) {
14080             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14081                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14082             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14083                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14084             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14085                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14086             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14087                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14088         }
14089      }
14090      if (q == p) *p++ = '-'; /* No castling rights */
14091      *p++ = ' ';
14092   }
14093
14094   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14095      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14096     /* En passant target square */
14097     if (move > backwardMostMove) {
14098         fromX = moveList[move - 1][0] - AAA;
14099         fromY = moveList[move - 1][1] - ONE;
14100         toX = moveList[move - 1][2] - AAA;
14101         toY = moveList[move - 1][3] - ONE;
14102         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14103             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14104             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14105             fromX == toX) {
14106             /* 2-square pawn move just happened */
14107             *p++ = toX + AAA;
14108             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14109         } else {
14110             *p++ = '-';
14111         }
14112     } else if(move == backwardMostMove) {
14113         // [HGM] perhaps we should always do it like this, and forget the above?
14114         if((signed char)boards[move][EP_STATUS] >= 0) {
14115             *p++ = boards[move][EP_STATUS] + AAA;
14116             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14117         } else {
14118             *p++ = '-';
14119         }
14120     } else {
14121         *p++ = '-';
14122     }
14123     *p++ = ' ';
14124   }
14125   }
14126
14127     /* [HGM] find reversible plies */
14128     {   int i = 0, j=move;
14129
14130         if (appData.debugMode) { int k;
14131             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14132             for(k=backwardMostMove; k<=forwardMostMove; k++)
14133                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14134
14135         }
14136
14137         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14138         if( j == backwardMostMove ) i += initialRulePlies;
14139         sprintf(p, "%d ", i);
14140         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14141     }
14142     /* Fullmove number */
14143     sprintf(p, "%d", (move / 2) + 1);
14144     
14145     return StrSave(buf);
14146 }
14147
14148 Boolean
14149 ParseFEN(board, blackPlaysFirst, fen)
14150     Board board;
14151      int *blackPlaysFirst;
14152      char *fen;
14153 {
14154     int i, j;
14155     char *p;
14156     int emptycount;
14157     ChessSquare piece;
14158
14159     p = fen;
14160
14161     /* [HGM] by default clear Crazyhouse holdings, if present */
14162     if(gameInfo.holdingsWidth) {
14163        for(i=0; i<BOARD_HEIGHT; i++) {
14164            board[i][0]             = EmptySquare; /* black holdings */
14165            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14166            board[i][1]             = (ChessSquare) 0; /* black counts */
14167            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14168        }
14169     }
14170
14171     /* Piece placement data */
14172     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14173         j = 0;
14174         for (;;) {
14175             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14176                 if (*p == '/') p++;
14177                 emptycount = gameInfo.boardWidth - j;
14178                 while (emptycount--)
14179                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14180                 break;
14181 #if(BOARD_FILES >= 10)
14182             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14183                 p++; emptycount=10;
14184                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14185                 while (emptycount--)
14186                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14187 #endif
14188             } else if (isdigit(*p)) {
14189                 emptycount = *p++ - '0';
14190                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14191                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14192                 while (emptycount--)
14193                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14194             } else if (*p == '+' || isalpha(*p)) {
14195                 if (j >= gameInfo.boardWidth) return FALSE;
14196                 if(*p=='+') {
14197                     piece = CharToPiece(*++p);
14198                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14199                     piece = (ChessSquare) (PROMOTED piece ); p++;
14200                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14201                 } else piece = CharToPiece(*p++);
14202
14203                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14204                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14205                     piece = (ChessSquare) (PROMOTED piece);
14206                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14207                     p++;
14208                 }
14209                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14210             } else {
14211                 return FALSE;
14212             }
14213         }
14214     }
14215     while (*p == '/' || *p == ' ') p++;
14216
14217     /* [HGM] look for Crazyhouse holdings here */
14218     while(*p==' ') p++;
14219     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14220         if(*p == '[') p++;
14221         if(*p == '-' ) *p++; /* empty holdings */ else {
14222             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14223             /* if we would allow FEN reading to set board size, we would   */
14224             /* have to add holdings and shift the board read so far here   */
14225             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14226                 *p++;
14227                 if((int) piece >= (int) BlackPawn ) {
14228                     i = (int)piece - (int)BlackPawn;
14229                     i = PieceToNumber((ChessSquare)i);
14230                     if( i >= gameInfo.holdingsSize ) return FALSE;
14231                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14232                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14233                 } else {
14234                     i = (int)piece - (int)WhitePawn;
14235                     i = PieceToNumber((ChessSquare)i);
14236                     if( i >= gameInfo.holdingsSize ) return FALSE;
14237                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14238                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14239                 }
14240             }
14241         }
14242         if(*p == ']') *p++;
14243     }
14244
14245     while(*p == ' ') p++;
14246
14247     /* Active color */
14248     switch (*p++) {
14249       case 'w':
14250         *blackPlaysFirst = FALSE;
14251         break;
14252       case 'b': 
14253         *blackPlaysFirst = TRUE;
14254         break;
14255       default:
14256         return FALSE;
14257     }
14258
14259     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14260     /* return the extra info in global variiables             */
14261
14262     /* set defaults in case FEN is incomplete */
14263     board[EP_STATUS] = EP_UNKNOWN;
14264     for(i=0; i<nrCastlingRights; i++ ) {
14265         board[CASTLING][i] =
14266             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14267     }   /* assume possible unless obviously impossible */
14268     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14269     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14270     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14271                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14272     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14273     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14274     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14275                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14276     FENrulePlies = 0;
14277
14278     while(*p==' ') p++;
14279     if(nrCastlingRights) {
14280       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14281           /* castling indicator present, so default becomes no castlings */
14282           for(i=0; i<nrCastlingRights; i++ ) {
14283                  board[CASTLING][i] = NoRights;
14284           }
14285       }
14286       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14287              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14288              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14289              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14290         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14291
14292         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14293             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14294             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14295         }
14296         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14297             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14298         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14299                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14300         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14301                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14302         switch(c) {
14303           case'K':
14304               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14305               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14306               board[CASTLING][2] = whiteKingFile;
14307               break;
14308           case'Q':
14309               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14310               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14311               board[CASTLING][2] = whiteKingFile;
14312               break;
14313           case'k':
14314               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14315               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14316               board[CASTLING][5] = blackKingFile;
14317               break;
14318           case'q':
14319               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14320               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14321               board[CASTLING][5] = blackKingFile;
14322           case '-':
14323               break;
14324           default: /* FRC castlings */
14325               if(c >= 'a') { /* black rights */
14326                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14327                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14328                   if(i == BOARD_RGHT) break;
14329                   board[CASTLING][5] = i;
14330                   c -= AAA;
14331                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14332                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14333                   if(c > i)
14334                       board[CASTLING][3] = c;
14335                   else
14336                       board[CASTLING][4] = c;
14337               } else { /* white rights */
14338                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14339                     if(board[0][i] == WhiteKing) break;
14340                   if(i == BOARD_RGHT) break;
14341                   board[CASTLING][2] = i;
14342                   c -= AAA - 'a' + 'A';
14343                   if(board[0][c] >= WhiteKing) break;
14344                   if(c > i)
14345                       board[CASTLING][0] = c;
14346                   else
14347                       board[CASTLING][1] = c;
14348               }
14349         }
14350       }
14351       for(i=0; i<nrCastlingRights; i++)
14352         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14353     if (appData.debugMode) {
14354         fprintf(debugFP, "FEN castling rights:");
14355         for(i=0; i<nrCastlingRights; i++)
14356         fprintf(debugFP, " %d", board[CASTLING][i]);
14357         fprintf(debugFP, "\n");
14358     }
14359
14360       while(*p==' ') p++;
14361     }
14362
14363     /* read e.p. field in games that know e.p. capture */
14364     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14365        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14366       if(*p=='-') {
14367         p++; board[EP_STATUS] = EP_NONE;
14368       } else {
14369          char c = *p++ - AAA;
14370
14371          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14372          if(*p >= '0' && *p <='9') *p++;
14373          board[EP_STATUS] = c;
14374       }
14375     }
14376
14377
14378     if(sscanf(p, "%d", &i) == 1) {
14379         FENrulePlies = i; /* 50-move ply counter */
14380         /* (The move number is still ignored)    */
14381     }
14382
14383     return TRUE;
14384 }
14385       
14386 void
14387 EditPositionPasteFEN(char *fen)
14388 {
14389   if (fen != NULL) {
14390     Board initial_position;
14391
14392     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14393       DisplayError(_("Bad FEN position in clipboard"), 0);
14394       return ;
14395     } else {
14396       int savedBlackPlaysFirst = blackPlaysFirst;
14397       EditPositionEvent();
14398       blackPlaysFirst = savedBlackPlaysFirst;
14399       CopyBoard(boards[0], initial_position);
14400       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14401       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14402       DisplayBothClocks();
14403       DrawPosition(FALSE, boards[currentMove]);
14404     }
14405   }
14406 }
14407
14408 static char cseq[12] = "\\   ";
14409
14410 Boolean set_cont_sequence(char *new_seq)
14411 {
14412     int len;
14413     Boolean ret;
14414
14415     // handle bad attempts to set the sequence
14416         if (!new_seq)
14417                 return 0; // acceptable error - no debug
14418
14419     len = strlen(new_seq);
14420     ret = (len > 0) && (len < sizeof(cseq));
14421     if (ret)
14422         strcpy(cseq, new_seq);
14423     else if (appData.debugMode)
14424         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14425     return ret;
14426 }
14427
14428 /*
14429     reformat a source message so words don't cross the width boundary.  internal
14430     newlines are not removed.  returns the wrapped size (no null character unless
14431     included in source message).  If dest is NULL, only calculate the size required
14432     for the dest buffer.  lp argument indicats line position upon entry, and it's
14433     passed back upon exit.
14434 */
14435 int wrap(char *dest, char *src, int count, int width, int *lp)
14436 {
14437     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14438
14439     cseq_len = strlen(cseq);
14440     old_line = line = *lp;
14441     ansi = len = clen = 0;
14442
14443     for (i=0; i < count; i++)
14444     {
14445         if (src[i] == '\033')
14446             ansi = 1;
14447
14448         // if we hit the width, back up
14449         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14450         {
14451             // store i & len in case the word is too long
14452             old_i = i, old_len = len;
14453
14454             // find the end of the last word
14455             while (i && src[i] != ' ' && src[i] != '\n')
14456             {
14457                 i--;
14458                 len--;
14459             }
14460
14461             // word too long?  restore i & len before splitting it
14462             if ((old_i-i+clen) >= width)
14463             {
14464                 i = old_i;
14465                 len = old_len;
14466             }
14467
14468             // extra space?
14469             if (i && src[i-1] == ' ')
14470                 len--;
14471
14472             if (src[i] != ' ' && src[i] != '\n')
14473             {
14474                 i--;
14475                 if (len)
14476                     len--;
14477             }
14478
14479             // now append the newline and continuation sequence
14480             if (dest)
14481                 dest[len] = '\n';
14482             len++;
14483             if (dest)
14484                 strncpy(dest+len, cseq, cseq_len);
14485             len += cseq_len;
14486             line = cseq_len;
14487             clen = cseq_len;
14488             continue;
14489         }
14490
14491         if (dest)
14492             dest[len] = src[i];
14493         len++;
14494         if (!ansi)
14495             line++;
14496         if (src[i] == '\n')
14497             line = 0;
14498         if (src[i] == 'm')
14499             ansi = 0;
14500     }
14501     if (dest && appData.debugMode)
14502     {
14503         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14504             count, width, line, len, *lp);
14505         show_bytes(debugFP, src, count);
14506         fprintf(debugFP, "\ndest: ");
14507         show_bytes(debugFP, dest, len);
14508         fprintf(debugFP, "\n");
14509     }
14510     *lp = dest ? line : old_line;
14511
14512     return len;
14513 }
14514
14515 // [HGM] vari: routines for shelving variations
14516
14517 void 
14518 PushTail(int firstMove, int lastMove)
14519 {
14520         int i, j, nrMoves = lastMove - firstMove;
14521
14522         if(appData.icsActive) { // only in local mode
14523                 forwardMostMove = currentMove; // mimic old ICS behavior
14524                 return;
14525         }
14526         if(storedGames >= MAX_VARIATIONS-1) return;
14527
14528         // push current tail of game on stack
14529         savedResult[storedGames] = gameInfo.result;
14530         savedDetails[storedGames] = gameInfo.resultDetails;
14531         gameInfo.resultDetails = NULL;
14532         savedFirst[storedGames] = firstMove;
14533         savedLast [storedGames] = lastMove;
14534         savedFramePtr[storedGames] = framePtr;
14535         framePtr -= nrMoves; // reserve space for the boards
14536         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14537             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14538             for(j=0; j<MOVE_LEN; j++)
14539                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14540             for(j=0; j<2*MOVE_LEN; j++)
14541                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14542             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14543             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14544             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14545             pvInfoList[firstMove+i-1].depth = 0;
14546             commentList[framePtr+i] = commentList[firstMove+i];
14547             commentList[firstMove+i] = NULL;
14548         }
14549
14550         storedGames++;
14551         forwardMostMove = currentMove; // truncte game so we can start variation
14552         if(storedGames == 1) GreyRevert(FALSE);
14553 }
14554
14555 Boolean
14556 PopTail(Boolean annotate)
14557 {
14558         int i, j, nrMoves;
14559         char buf[8000], moveBuf[20];
14560
14561         if(appData.icsActive) return FALSE; // only in local mode
14562         if(!storedGames) return FALSE; // sanity
14563
14564         storedGames--;
14565         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14566         nrMoves = savedLast[storedGames] - currentMove;
14567         if(annotate) {
14568                 int cnt = 10;
14569                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14570                 else strcpy(buf, "(");
14571                 for(i=currentMove; i<forwardMostMove; i++) {
14572                         if(WhiteOnMove(i))
14573                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14574                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14575                         strcat(buf, moveBuf);
14576                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14577                 }
14578                 strcat(buf, ")");
14579         }
14580         for(i=1; i<nrMoves; i++) { // copy last variation back
14581             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14582             for(j=0; j<MOVE_LEN; j++)
14583                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14584             for(j=0; j<2*MOVE_LEN; j++)
14585                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14586             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14587             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14588             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14589             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14590             commentList[currentMove+i] = commentList[framePtr+i];
14591             commentList[framePtr+i] = NULL;
14592         }
14593         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14594         framePtr = savedFramePtr[storedGames];
14595         gameInfo.result = savedResult[storedGames];
14596         if(gameInfo.resultDetails != NULL) {
14597             free(gameInfo.resultDetails);
14598       }
14599         gameInfo.resultDetails = savedDetails[storedGames];
14600         forwardMostMove = currentMove + nrMoves;
14601         if(storedGames == 0) GreyRevert(TRUE);
14602         return TRUE;
14603 }
14604
14605 void 
14606 CleanupTail()
14607 {       // remove all shelved variations
14608         int i;
14609         for(i=0; i<storedGames; i++) {
14610             if(savedDetails[i])
14611                 free(savedDetails[i]);
14612             savedDetails[i] = NULL;
14613         }
14614         for(i=framePtr; i<MAX_MOVES; i++) {
14615                 if(commentList[i]) free(commentList[i]);
14616                 commentList[i] = NULL;
14617         }
14618         framePtr = MAX_MOVES-1;
14619         storedGames = 0;
14620 }