Right-click refactoring: step II
[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 = 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                 } else {
2711                     started = STARTED_CHATTER;
2712                 }
2713                 continue;
2714             }
2715
2716             if (looking_at(buf, &i, "Black Strength :") ||
2717                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2718                 looking_at(buf, &i, "<10>") ||
2719                 looking_at(buf, &i, "#@#")) {
2720                 /* Wrong board style */
2721                 loggedOn = TRUE;
2722                 SendToICS(ics_prefix);
2723                 SendToICS("set style 12\n");
2724                 SendToICS(ics_prefix);
2725                 SendToICS("refresh\n");
2726                 continue;
2727             }
2728             
2729             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2730                 ICSInitScript();
2731                 have_sent_ICS_logon = 1;
2732                 continue;
2733             }
2734               
2735             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2736                 (looking_at(buf, &i, "\n<12> ") ||
2737                  looking_at(buf, &i, "<12> "))) {
2738                 loggedOn = TRUE;
2739                 if (oldi > next_out) {
2740                     SendToPlayer(&buf[next_out], oldi - next_out);
2741                 }
2742                 next_out = i;
2743                 started = STARTED_BOARD;
2744                 parse_pos = 0;
2745                 continue;
2746             }
2747
2748             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2749                 looking_at(buf, &i, "<b1> ")) {
2750                 if (oldi > next_out) {
2751                     SendToPlayer(&buf[next_out], oldi - next_out);
2752                 }
2753                 next_out = i;
2754                 started = STARTED_HOLDINGS;
2755                 parse_pos = 0;
2756                 continue;
2757             }
2758
2759             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2760                 loggedOn = TRUE;
2761                 /* Header for a move list -- first line */
2762
2763                 switch (ics_getting_history) {
2764                   case H_FALSE:
2765                     switch (gameMode) {
2766                       case IcsIdle:
2767                       case BeginningOfGame:
2768                         /* User typed "moves" or "oldmoves" while we
2769                            were idle.  Pretend we asked for these
2770                            moves and soak them up so user can step
2771                            through them and/or save them.
2772                            */
2773                         Reset(FALSE, TRUE);
2774                         gameMode = IcsObserving;
2775                         ModeHighlight();
2776                         ics_gamenum = -1;
2777                         ics_getting_history = H_GOT_UNREQ_HEADER;
2778                         break;
2779                       case EditGame: /*?*/
2780                       case EditPosition: /*?*/
2781                         /* Should above feature work in these modes too? */
2782                         /* For now it doesn't */
2783                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2784                         break;
2785                       default:
2786                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2787                         break;
2788                     }
2789                     break;
2790                   case H_REQUESTED:
2791                     /* Is this the right one? */
2792                     if (gameInfo.white && gameInfo.black &&
2793                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2794                         strcmp(gameInfo.black, star_match[2]) == 0) {
2795                         /* All is well */
2796                         ics_getting_history = H_GOT_REQ_HEADER;
2797                     }
2798                     break;
2799                   case H_GOT_REQ_HEADER:
2800                   case H_GOT_UNREQ_HEADER:
2801                   case H_GOT_UNWANTED_HEADER:
2802                   case H_GETTING_MOVES:
2803                     /* Should not happen */
2804                     DisplayError(_("Error gathering move list: two headers"), 0);
2805                     ics_getting_history = H_FALSE;
2806                     break;
2807                 }
2808
2809                 /* Save player ratings into gameInfo if needed */
2810                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2811                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2812                     (gameInfo.whiteRating == -1 ||
2813                      gameInfo.blackRating == -1)) {
2814
2815                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2816                     gameInfo.blackRating = string_to_rating(star_match[3]);
2817                     if (appData.debugMode)
2818                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2819                               gameInfo.whiteRating, gameInfo.blackRating);
2820                 }
2821                 continue;
2822             }
2823
2824             if (looking_at(buf, &i,
2825               "* * match, initial time: * minute*, increment: * second")) {
2826                 /* Header for a move list -- second line */
2827                 /* Initial board will follow if this is a wild game */
2828                 if (gameInfo.event != NULL) free(gameInfo.event);
2829                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2830                 gameInfo.event = StrSave(str);
2831                 /* [HGM] we switched variant. Translate boards if needed. */
2832                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2833                 continue;
2834             }
2835
2836             if (looking_at(buf, &i, "Move  ")) {
2837                 /* Beginning of a move list */
2838                 switch (ics_getting_history) {
2839                   case H_FALSE:
2840                     /* Normally should not happen */
2841                     /* Maybe user hit reset while we were parsing */
2842                     break;
2843                   case H_REQUESTED:
2844                     /* Happens if we are ignoring a move list that is not
2845                      * the one we just requested.  Common if the user
2846                      * tries to observe two games without turning off
2847                      * getMoveList */
2848                     break;
2849                   case H_GETTING_MOVES:
2850                     /* Should not happen */
2851                     DisplayError(_("Error gathering move list: nested"), 0);
2852                     ics_getting_history = H_FALSE;
2853                     break;
2854                   case H_GOT_REQ_HEADER:
2855                     ics_getting_history = H_GETTING_MOVES;
2856                     started = STARTED_MOVES;
2857                     parse_pos = 0;
2858                     if (oldi > next_out) {
2859                         SendToPlayer(&buf[next_out], oldi - next_out);
2860                     }
2861                     break;
2862                   case H_GOT_UNREQ_HEADER:
2863                     ics_getting_history = H_GETTING_MOVES;
2864                     started = STARTED_MOVES_NOHIDE;
2865                     parse_pos = 0;
2866                     break;
2867                   case H_GOT_UNWANTED_HEADER:
2868                     ics_getting_history = H_FALSE;
2869                     break;
2870                 }
2871                 continue;
2872             }                           
2873             
2874             if (looking_at(buf, &i, "% ") ||
2875                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2876                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2877                 if(suppressKibitz) next_out = i;
2878                 savingComment = FALSE;
2879                 suppressKibitz = 0;
2880                 switch (started) {
2881                   case STARTED_MOVES:
2882                   case STARTED_MOVES_NOHIDE:
2883                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2884                     parse[parse_pos + i - oldi] = NULLCHAR;
2885                     ParseGameHistory(parse);
2886 #if ZIPPY
2887                     if (appData.zippyPlay && first.initDone) {
2888                         FeedMovesToProgram(&first, forwardMostMove);
2889                         if (gameMode == IcsPlayingWhite) {
2890                             if (WhiteOnMove(forwardMostMove)) {
2891                                 if (first.sendTime) {
2892                                   if (first.useColors) {
2893                                     SendToProgram("black\n", &first); 
2894                                   }
2895                                   SendTimeRemaining(&first, TRUE);
2896                                 }
2897                                 if (first.useColors) {
2898                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2899                                 }
2900                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2901                                 first.maybeThinking = TRUE;
2902                             } else {
2903                                 if (first.usePlayother) {
2904                                   if (first.sendTime) {
2905                                     SendTimeRemaining(&first, TRUE);
2906                                   }
2907                                   SendToProgram("playother\n", &first);
2908                                   firstMove = FALSE;
2909                                 } else {
2910                                   firstMove = TRUE;
2911                                 }
2912                             }
2913                         } else if (gameMode == IcsPlayingBlack) {
2914                             if (!WhiteOnMove(forwardMostMove)) {
2915                                 if (first.sendTime) {
2916                                   if (first.useColors) {
2917                                     SendToProgram("white\n", &first);
2918                                   }
2919                                   SendTimeRemaining(&first, FALSE);
2920                                 }
2921                                 if (first.useColors) {
2922                                   SendToProgram("black\n", &first);
2923                                 }
2924                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2925                                 first.maybeThinking = TRUE;
2926                             } else {
2927                                 if (first.usePlayother) {
2928                                   if (first.sendTime) {
2929                                     SendTimeRemaining(&first, FALSE);
2930                                   }
2931                                   SendToProgram("playother\n", &first);
2932                                   firstMove = FALSE;
2933                                 } else {
2934                                   firstMove = TRUE;
2935                                 }
2936                             }
2937                         }                       
2938                     }
2939 #endif
2940                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2941                         /* Moves came from oldmoves or moves command
2942                            while we weren't doing anything else.
2943                            */
2944                         currentMove = forwardMostMove;
2945                         ClearHighlights();/*!!could figure this out*/
2946                         flipView = appData.flipView;
2947                         DrawPosition(TRUE, boards[currentMove]);
2948                         DisplayBothClocks();
2949                         sprintf(str, "%s vs. %s",
2950                                 gameInfo.white, gameInfo.black);
2951                         DisplayTitle(str);
2952                         gameMode = IcsIdle;
2953                     } else {
2954                         /* Moves were history of an active game */
2955                         if (gameInfo.resultDetails != NULL) {
2956                             free(gameInfo.resultDetails);
2957                             gameInfo.resultDetails = NULL;
2958                         }
2959                     }
2960                     HistorySet(parseList, backwardMostMove,
2961                                forwardMostMove, currentMove-1);
2962                     DisplayMove(currentMove - 1);
2963                     if (started == STARTED_MOVES) next_out = i;
2964                     started = STARTED_NONE;
2965                     ics_getting_history = H_FALSE;
2966                     break;
2967
2968                   case STARTED_OBSERVE:
2969                     started = STARTED_NONE;
2970                     SendToICS(ics_prefix);
2971                     SendToICS("refresh\n");
2972                     break;
2973
2974                   default:
2975                     break;
2976                 }
2977                 if(bookHit) { // [HGM] book: simulate book reply
2978                     static char bookMove[MSG_SIZ]; // a bit generous?
2979
2980                     programStats.nodes = programStats.depth = programStats.time = 
2981                     programStats.score = programStats.got_only_move = 0;
2982                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2983
2984                     strcpy(bookMove, "move ");
2985                     strcat(bookMove, bookHit);
2986                     HandleMachineMove(bookMove, &first);
2987                 }
2988                 continue;
2989             }
2990             
2991             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2992                  started == STARTED_HOLDINGS ||
2993                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2994                 /* Accumulate characters in move list or board */
2995                 parse[parse_pos++] = buf[i];
2996             }
2997             
2998             /* Start of game messages.  Mostly we detect start of game
2999                when the first board image arrives.  On some versions
3000                of the ICS, though, we need to do a "refresh" after starting
3001                to observe in order to get the current board right away. */
3002             if (looking_at(buf, &i, "Adding game * to observation list")) {
3003                 started = STARTED_OBSERVE;
3004                 continue;
3005             }
3006
3007             /* Handle auto-observe */
3008             if (appData.autoObserve &&
3009                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3010                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3011                 char *player;
3012                 /* Choose the player that was highlighted, if any. */
3013                 if (star_match[0][0] == '\033' ||
3014                     star_match[1][0] != '\033') {
3015                     player = star_match[0];
3016                 } else {
3017                     player = star_match[2];
3018                 }
3019                 sprintf(str, "%sobserve %s\n",
3020                         ics_prefix, StripHighlightAndTitle(player));
3021                 SendToICS(str);
3022
3023                 /* Save ratings from notify string */
3024                 strcpy(player1Name, star_match[0]);
3025                 player1Rating = string_to_rating(star_match[1]);
3026                 strcpy(player2Name, star_match[2]);
3027                 player2Rating = string_to_rating(star_match[3]);
3028
3029                 if (appData.debugMode)
3030                   fprintf(debugFP, 
3031                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3032                           player1Name, player1Rating,
3033                           player2Name, player2Rating);
3034
3035                 continue;
3036             }
3037
3038             /* Deal with automatic examine mode after a game,
3039                and with IcsObserving -> IcsExamining transition */
3040             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3041                 looking_at(buf, &i, "has made you an examiner of game *")) {
3042
3043                 int gamenum = atoi(star_match[0]);
3044                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3045                     gamenum == ics_gamenum) {
3046                     /* We were already playing or observing this game;
3047                        no need to refetch history */
3048                     gameMode = IcsExamining;
3049                     if (pausing) {
3050                         pauseExamForwardMostMove = forwardMostMove;
3051                     } else if (currentMove < forwardMostMove) {
3052                         ForwardInner(forwardMostMove);
3053                     }
3054                 } else {
3055                     /* I don't think this case really can happen */
3056                     SendToICS(ics_prefix);
3057                     SendToICS("refresh\n");
3058                 }
3059                 continue;
3060             }    
3061             
3062             /* Error messages */
3063 //          if (ics_user_moved) {
3064             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3065                 if (looking_at(buf, &i, "Illegal move") ||
3066                     looking_at(buf, &i, "Not a legal move") ||
3067                     looking_at(buf, &i, "Your king is in check") ||
3068                     looking_at(buf, &i, "It isn't your turn") ||
3069                     looking_at(buf, &i, "It is not your move")) {
3070                     /* Illegal move */
3071                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3072                         currentMove = --forwardMostMove;
3073                         DisplayMove(currentMove - 1); /* before DMError */
3074                         DrawPosition(FALSE, boards[currentMove]);
3075                         SwitchClocks();
3076                         DisplayBothClocks();
3077                     }
3078                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3079                     ics_user_moved = 0;
3080                     continue;
3081                 }
3082             }
3083
3084             if (looking_at(buf, &i, "still have time") ||
3085                 looking_at(buf, &i, "not out of time") ||
3086                 looking_at(buf, &i, "either player is out of time") ||
3087                 looking_at(buf, &i, "has timeseal; checking")) {
3088                 /* We must have called his flag a little too soon */
3089                 whiteFlag = blackFlag = FALSE;
3090                 continue;
3091             }
3092
3093             if (looking_at(buf, &i, "added * seconds to") ||
3094                 looking_at(buf, &i, "seconds were added to")) {
3095                 /* Update the clocks */
3096                 SendToICS(ics_prefix);
3097                 SendToICS("refresh\n");
3098                 continue;
3099             }
3100
3101             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3102                 ics_clock_paused = TRUE;
3103                 StopClocks();
3104                 continue;
3105             }
3106
3107             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3108                 ics_clock_paused = FALSE;
3109                 StartClocks();
3110                 continue;
3111             }
3112
3113             /* Grab player ratings from the Creating: message.
3114                Note we have to check for the special case when
3115                the ICS inserts things like [white] or [black]. */
3116             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3117                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3118                 /* star_matches:
3119                    0    player 1 name (not necessarily white)
3120                    1    player 1 rating
3121                    2    empty, white, or black (IGNORED)
3122                    3    player 2 name (not necessarily black)
3123                    4    player 2 rating
3124                    
3125                    The names/ratings are sorted out when the game
3126                    actually starts (below).
3127                 */
3128                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3129                 player1Rating = string_to_rating(star_match[1]);
3130                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3131                 player2Rating = string_to_rating(star_match[4]);
3132
3133                 if (appData.debugMode)
3134                   fprintf(debugFP, 
3135                           "Ratings from 'Creating:' %s %d, %s %d\n",
3136                           player1Name, player1Rating,
3137                           player2Name, player2Rating);
3138
3139                 continue;
3140             }
3141             
3142             /* Improved generic start/end-of-game messages */
3143             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3144                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3145                 /* If tkind == 0: */
3146                 /* star_match[0] is the game number */
3147                 /*           [1] is the white player's name */
3148                 /*           [2] is the black player's name */
3149                 /* For end-of-game: */
3150                 /*           [3] is the reason for the game end */
3151                 /*           [4] is a PGN end game-token, preceded by " " */
3152                 /* For start-of-game: */
3153                 /*           [3] begins with "Creating" or "Continuing" */
3154                 /*           [4] is " *" or empty (don't care). */
3155                 int gamenum = atoi(star_match[0]);
3156                 char *whitename, *blackname, *why, *endtoken;
3157                 ChessMove endtype = (ChessMove) 0;
3158
3159                 if (tkind == 0) {
3160                   whitename = star_match[1];
3161                   blackname = star_match[2];
3162                   why = star_match[3];
3163                   endtoken = star_match[4];
3164                 } else {
3165                   whitename = star_match[1];
3166                   blackname = star_match[3];
3167                   why = star_match[5];
3168                   endtoken = star_match[6];
3169                 }
3170
3171                 /* Game start messages */
3172                 if (strncmp(why, "Creating ", 9) == 0 ||
3173                     strncmp(why, "Continuing ", 11) == 0) {
3174                     gs_gamenum = gamenum;
3175                     strcpy(gs_kind, strchr(why, ' ') + 1);
3176                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3177 #if ZIPPY
3178                     if (appData.zippyPlay) {
3179                         ZippyGameStart(whitename, blackname);
3180                     }
3181 #endif /*ZIPPY*/
3182                     continue;
3183                 }
3184
3185                 /* Game end messages */
3186                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3187                     ics_gamenum != gamenum) {
3188                     continue;
3189                 }
3190                 while (endtoken[0] == ' ') endtoken++;
3191                 switch (endtoken[0]) {
3192                   case '*':
3193                   default:
3194                     endtype = GameUnfinished;
3195                     break;
3196                   case '0':
3197                     endtype = BlackWins;
3198                     break;
3199                   case '1':
3200                     if (endtoken[1] == '/')
3201                       endtype = GameIsDrawn;
3202                     else
3203                       endtype = WhiteWins;
3204                     break;
3205                 }
3206                 GameEnds(endtype, why, GE_ICS);
3207 #if ZIPPY
3208                 if (appData.zippyPlay && first.initDone) {
3209                     ZippyGameEnd(endtype, why);
3210                     if (first.pr == NULL) {
3211                       /* Start the next process early so that we'll
3212                          be ready for the next challenge */
3213                       StartChessProgram(&first);
3214                     }
3215                     /* Send "new" early, in case this command takes
3216                        a long time to finish, so that we'll be ready
3217                        for the next challenge. */
3218                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3219                     Reset(TRUE, TRUE);
3220                 }
3221 #endif /*ZIPPY*/
3222                 continue;
3223             }
3224
3225             if (looking_at(buf, &i, "Removing game * from observation") ||
3226                 looking_at(buf, &i, "no longer observing game *") ||
3227                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3228                 if (gameMode == IcsObserving &&
3229                     atoi(star_match[0]) == ics_gamenum)
3230                   {
3231                       /* icsEngineAnalyze */
3232                       if (appData.icsEngineAnalyze) {
3233                             ExitAnalyzeMode();
3234                             ModeHighlight();
3235                       }
3236                       StopClocks();
3237                       gameMode = IcsIdle;
3238                       ics_gamenum = -1;
3239                       ics_user_moved = FALSE;
3240                   }
3241                 continue;
3242             }
3243
3244             if (looking_at(buf, &i, "no longer examining game *")) {
3245                 if (gameMode == IcsExamining &&
3246                     atoi(star_match[0]) == ics_gamenum)
3247                   {
3248                       gameMode = IcsIdle;
3249                       ics_gamenum = -1;
3250                       ics_user_moved = FALSE;
3251                   }
3252                 continue;
3253             }
3254
3255             /* Advance leftover_start past any newlines we find,
3256                so only partial lines can get reparsed */
3257             if (looking_at(buf, &i, "\n")) {
3258                 prevColor = curColor;
3259                 if (curColor != ColorNormal) {
3260                     if (oldi > next_out) {
3261                         SendToPlayer(&buf[next_out], oldi - next_out);
3262                         next_out = oldi;
3263                     }
3264                     Colorize(ColorNormal, FALSE);
3265                     curColor = ColorNormal;
3266                 }
3267                 if (started == STARTED_BOARD) {
3268                     started = STARTED_NONE;
3269                     parse[parse_pos] = NULLCHAR;
3270                     ParseBoard12(parse);
3271                     ics_user_moved = 0;
3272
3273                     /* Send premove here */
3274                     if (appData.premove) {
3275                       char str[MSG_SIZ];
3276                       if (currentMove == 0 &&
3277                           gameMode == IcsPlayingWhite &&
3278                           appData.premoveWhite) {
3279                         sprintf(str, "%s\n", appData.premoveWhiteText);
3280                         if (appData.debugMode)
3281                           fprintf(debugFP, "Sending premove:\n");
3282                         SendToICS(str);
3283                       } else if (currentMove == 1 &&
3284                                  gameMode == IcsPlayingBlack &&
3285                                  appData.premoveBlack) {
3286                         sprintf(str, "%s\n", appData.premoveBlackText);
3287                         if (appData.debugMode)
3288                           fprintf(debugFP, "Sending premove:\n");
3289                         SendToICS(str);
3290                       } else if (gotPremove) {
3291                         gotPremove = 0;
3292                         ClearPremoveHighlights();
3293                         if (appData.debugMode)
3294                           fprintf(debugFP, "Sending premove:\n");
3295                           UserMoveEvent(premoveFromX, premoveFromY, 
3296                                         premoveToX, premoveToY, 
3297                                         premovePromoChar);
3298                       }
3299                     }
3300
3301                     /* Usually suppress following prompt */
3302                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3303                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3304                         if (looking_at(buf, &i, "*% ")) {
3305                             savingComment = FALSE;
3306                             suppressKibitz = 0;
3307                         }
3308                     }
3309                     next_out = i;
3310                 } else if (started == STARTED_HOLDINGS) {
3311                     int gamenum;
3312                     char new_piece[MSG_SIZ];
3313                     started = STARTED_NONE;
3314                     parse[parse_pos] = NULLCHAR;
3315                     if (appData.debugMode)
3316                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3317                                                         parse, currentMove);
3318                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3319                         gamenum == ics_gamenum) {
3320                         if (gameInfo.variant == VariantNormal) {
3321                           /* [HGM] We seem to switch variant during a game!
3322                            * Presumably no holdings were displayed, so we have
3323                            * to move the position two files to the right to
3324                            * create room for them!
3325                            */
3326                           VariantClass newVariant;
3327                           switch(gameInfo.boardWidth) { // base guess on board width
3328                                 case 9:  newVariant = VariantShogi; break;
3329                                 case 10: newVariant = VariantGreat; break;
3330                                 default: newVariant = VariantCrazyhouse; break;
3331                           }
3332                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3333                           /* Get a move list just to see the header, which
3334                              will tell us whether this is really bug or zh */
3335                           if (ics_getting_history == H_FALSE) {
3336                             ics_getting_history = H_REQUESTED;
3337                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3338                             SendToICS(str);
3339                           }
3340                         }
3341                         new_piece[0] = NULLCHAR;
3342                         sscanf(parse, "game %d white [%s black [%s <- %s",
3343                                &gamenum, white_holding, black_holding,
3344                                new_piece);
3345                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3346                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3347                         /* [HGM] copy holdings to board holdings area */
3348                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3349                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3350                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3351 #if ZIPPY
3352                         if (appData.zippyPlay && first.initDone) {
3353                             ZippyHoldings(white_holding, black_holding,
3354                                           new_piece);
3355                         }
3356 #endif /*ZIPPY*/
3357                         if (tinyLayout || smallLayout) {
3358                             char wh[16], bh[16];
3359                             PackHolding(wh, white_holding);
3360                             PackHolding(bh, black_holding);
3361                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3362                                     gameInfo.white, gameInfo.black);
3363                         } else {
3364                             sprintf(str, "%s [%s] vs. %s [%s]",
3365                                     gameInfo.white, white_holding,
3366                                     gameInfo.black, black_holding);
3367                         }
3368
3369                         DrawPosition(FALSE, boards[currentMove]);
3370                         DisplayTitle(str);
3371                     }
3372                     /* Suppress following prompt */
3373                     if (looking_at(buf, &i, "*% ")) {
3374                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3375                         savingComment = FALSE;
3376                         suppressKibitz = 0;
3377                     }
3378                     next_out = i;
3379                 }
3380                 continue;
3381             }
3382
3383             i++;                /* skip unparsed character and loop back */
3384         }
3385         
3386         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3387 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3388 //          SendToPlayer(&buf[next_out], i - next_out);
3389             started != STARTED_HOLDINGS && leftover_start > next_out) {
3390             SendToPlayer(&buf[next_out], leftover_start - next_out);
3391             next_out = i;
3392         }
3393         
3394         leftover_len = buf_len - leftover_start;
3395         /* if buffer ends with something we couldn't parse,
3396            reparse it after appending the next read */
3397         
3398     } else if (count == 0) {
3399         RemoveInputSource(isr);
3400         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3401     } else {
3402         DisplayFatalError(_("Error reading from ICS"), error, 1);
3403     }
3404 }
3405
3406
3407 /* Board style 12 looks like this:
3408    
3409    <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
3410    
3411  * The "<12> " is stripped before it gets to this routine.  The two
3412  * trailing 0's (flip state and clock ticking) are later addition, and
3413  * some chess servers may not have them, or may have only the first.
3414  * Additional trailing fields may be added in the future.  
3415  */
3416
3417 #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"
3418
3419 #define RELATION_OBSERVING_PLAYED    0
3420 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3421 #define RELATION_PLAYING_MYMOVE      1
3422 #define RELATION_PLAYING_NOTMYMOVE  -1
3423 #define RELATION_EXAMINING           2
3424 #define RELATION_ISOLATED_BOARD     -3
3425 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3426
3427 void
3428 ParseBoard12(string)
3429      char *string;
3430
3431     GameMode newGameMode;
3432     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3433     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3434     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3435     char to_play, board_chars[200];
3436     char move_str[500], str[500], elapsed_time[500];
3437     char black[32], white[32];
3438     Board board;
3439     int prevMove = currentMove;
3440     int ticking = 2;
3441     ChessMove moveType;
3442     int fromX, fromY, toX, toY;
3443     char promoChar;
3444     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3445     char *bookHit = NULL; // [HGM] book
3446     Boolean weird = FALSE, reqFlag = FALSE;
3447
3448     fromX = fromY = toX = toY = -1;
3449     
3450     newGame = FALSE;
3451
3452     if (appData.debugMode)
3453       fprintf(debugFP, _("Parsing board: %s\n"), string);
3454
3455     move_str[0] = NULLCHAR;
3456     elapsed_time[0] = NULLCHAR;
3457     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3458         int  i = 0, j;
3459         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3460             if(string[i] == ' ') { ranks++; files = 0; }
3461             else files++;
3462             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3463             i++;
3464         }
3465         for(j = 0; j <i; j++) board_chars[j] = string[j];
3466         board_chars[i] = '\0';
3467         string += i + 1;
3468     }
3469     n = sscanf(string, PATTERN, &to_play, &double_push,
3470                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3471                &gamenum, white, black, &relation, &basetime, &increment,
3472                &white_stren, &black_stren, &white_time, &black_time,
3473                &moveNum, str, elapsed_time, move_str, &ics_flip,
3474                &ticking);
3475
3476     if (n < 21) {
3477         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3478         DisplayError(str, 0);
3479         return;
3480     }
3481
3482     /* Convert the move number to internal form */
3483     moveNum = (moveNum - 1) * 2;
3484     if (to_play == 'B') moveNum++;
3485     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3486       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3487                         0, 1);
3488       return;
3489     }
3490     
3491     switch (relation) {
3492       case RELATION_OBSERVING_PLAYED:
3493       case RELATION_OBSERVING_STATIC:
3494         if (gamenum == -1) {
3495             /* Old ICC buglet */
3496             relation = RELATION_OBSERVING_STATIC;
3497         }
3498         newGameMode = IcsObserving;
3499         break;
3500       case RELATION_PLAYING_MYMOVE:
3501       case RELATION_PLAYING_NOTMYMOVE:
3502         newGameMode =
3503           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3504             IcsPlayingWhite : IcsPlayingBlack;
3505         break;
3506       case RELATION_EXAMINING:
3507         newGameMode = IcsExamining;
3508         break;
3509       case RELATION_ISOLATED_BOARD:
3510       default:
3511         /* Just display this board.  If user was doing something else,
3512            we will forget about it until the next board comes. */ 
3513         newGameMode = IcsIdle;
3514         break;
3515       case RELATION_STARTING_POSITION:
3516         newGameMode = gameMode;
3517         break;
3518     }
3519     
3520     /* Modify behavior for initial board display on move listing
3521        of wild games.
3522        */
3523     switch (ics_getting_history) {
3524       case H_FALSE:
3525       case H_REQUESTED:
3526         break;
3527       case H_GOT_REQ_HEADER:
3528       case H_GOT_UNREQ_HEADER:
3529         /* This is the initial position of the current game */
3530         gamenum = ics_gamenum;
3531         moveNum = 0;            /* old ICS bug workaround */
3532         if (to_play == 'B') {
3533           startedFromSetupPosition = TRUE;
3534           blackPlaysFirst = TRUE;
3535           moveNum = 1;
3536           if (forwardMostMove == 0) forwardMostMove = 1;
3537           if (backwardMostMove == 0) backwardMostMove = 1;
3538           if (currentMove == 0) currentMove = 1;
3539         }
3540         newGameMode = gameMode;
3541         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3542         break;
3543       case H_GOT_UNWANTED_HEADER:
3544         /* This is an initial board that we don't want */
3545         return;
3546       case H_GETTING_MOVES:
3547         /* Should not happen */
3548         DisplayError(_("Error gathering move list: extra board"), 0);
3549         ics_getting_history = H_FALSE;
3550         return;
3551     }
3552
3553    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3554                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3555      /* [HGM] We seem to have switched variant unexpectedly
3556       * Try to guess new variant from board size
3557       */
3558           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3559           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3560           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3561           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3562           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3563           if(!weird) newVariant = VariantNormal;
3564           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3565           /* Get a move list just to see the header, which
3566              will tell us whether this is really bug or zh */
3567           if (ics_getting_history == H_FALSE) {
3568             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3569             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3570             SendToICS(str);
3571           }
3572     }
3573     
3574     /* Take action if this is the first board of a new game, or of a
3575        different game than is currently being displayed.  */
3576     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3577         relation == RELATION_ISOLATED_BOARD) {
3578         
3579         /* Forget the old game and get the history (if any) of the new one */
3580         if (gameMode != BeginningOfGame) {
3581           Reset(TRUE, TRUE);
3582         }
3583         newGame = TRUE;
3584         if (appData.autoRaiseBoard) BoardToTop();
3585         prevMove = -3;
3586         if (gamenum == -1) {
3587             newGameMode = IcsIdle;
3588         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3589                    appData.getMoveList && !reqFlag) {
3590             /* Need to get game history */
3591             ics_getting_history = H_REQUESTED;
3592             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3593             SendToICS(str);
3594         }
3595         
3596         /* Initially flip the board to have black on the bottom if playing
3597            black or if the ICS flip flag is set, but let the user change
3598            it with the Flip View button. */
3599         flipView = appData.autoFlipView ? 
3600           (newGameMode == IcsPlayingBlack) || ics_flip :
3601           appData.flipView;
3602         
3603         /* Done with values from previous mode; copy in new ones */
3604         gameMode = newGameMode;
3605         ModeHighlight();
3606         ics_gamenum = gamenum;
3607         if (gamenum == gs_gamenum) {
3608             int klen = strlen(gs_kind);
3609             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3610             sprintf(str, "ICS %s", gs_kind);
3611             gameInfo.event = StrSave(str);
3612         } else {
3613             gameInfo.event = StrSave("ICS game");
3614         }
3615         gameInfo.site = StrSave(appData.icsHost);
3616         gameInfo.date = PGNDate();
3617         gameInfo.round = StrSave("-");
3618         gameInfo.white = StrSave(white);
3619         gameInfo.black = StrSave(black);
3620         timeControl = basetime * 60 * 1000;
3621         timeControl_2 = 0;
3622         timeIncrement = increment * 1000;
3623         movesPerSession = 0;
3624         gameInfo.timeControl = TimeControlTagValue();
3625         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3626   if (appData.debugMode) {
3627     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3628     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3629     setbuf(debugFP, NULL);
3630   }
3631
3632         gameInfo.outOfBook = NULL;
3633         
3634         /* Do we have the ratings? */
3635         if (strcmp(player1Name, white) == 0 &&
3636             strcmp(player2Name, black) == 0) {
3637             if (appData.debugMode)
3638               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3639                       player1Rating, player2Rating);
3640             gameInfo.whiteRating = player1Rating;
3641             gameInfo.blackRating = player2Rating;
3642         } else if (strcmp(player2Name, white) == 0 &&
3643                    strcmp(player1Name, black) == 0) {
3644             if (appData.debugMode)
3645               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3646                       player2Rating, player1Rating);
3647             gameInfo.whiteRating = player2Rating;
3648             gameInfo.blackRating = player1Rating;
3649         }
3650         player1Name[0] = player2Name[0] = NULLCHAR;
3651
3652         /* Silence shouts if requested */
3653         if (appData.quietPlay &&
3654             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3655             SendToICS(ics_prefix);
3656             SendToICS("set shout 0\n");
3657         }
3658     }
3659     
3660     /* Deal with midgame name changes */
3661     if (!newGame) {
3662         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3663             if (gameInfo.white) free(gameInfo.white);
3664             gameInfo.white = StrSave(white);
3665         }
3666         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3667             if (gameInfo.black) free(gameInfo.black);
3668             gameInfo.black = StrSave(black);
3669         }
3670     }
3671     
3672     /* Throw away game result if anything actually changes in examine mode */
3673     if (gameMode == IcsExamining && !newGame) {
3674         gameInfo.result = GameUnfinished;
3675         if (gameInfo.resultDetails != NULL) {
3676             free(gameInfo.resultDetails);
3677             gameInfo.resultDetails = NULL;
3678         }
3679     }
3680     
3681     /* In pausing && IcsExamining mode, we ignore boards coming
3682        in if they are in a different variation than we are. */
3683     if (pauseExamInvalid) return;
3684     if (pausing && gameMode == IcsExamining) {
3685         if (moveNum <= pauseExamForwardMostMove) {
3686             pauseExamInvalid = TRUE;
3687             forwardMostMove = pauseExamForwardMostMove;
3688             return;
3689         }
3690     }
3691     
3692   if (appData.debugMode) {
3693     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3694   }
3695     /* Parse the board */
3696     for (k = 0; k < ranks; k++) {
3697       for (j = 0; j < files; j++)
3698         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3699       if(gameInfo.holdingsWidth > 1) {
3700            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3701            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3702       }
3703     }
3704     CopyBoard(boards[moveNum], board);
3705     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3706     if (moveNum == 0) {
3707         startedFromSetupPosition =
3708           !CompareBoards(board, initialPosition);
3709         if(startedFromSetupPosition)
3710             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3711     }
3712
3713     /* [HGM] Set castling rights. Take the outermost Rooks,
3714        to make it also work for FRC opening positions. Note that board12
3715        is really defective for later FRC positions, as it has no way to
3716        indicate which Rook can castle if they are on the same side of King.
3717        For the initial position we grant rights to the outermost Rooks,
3718        and remember thos rights, and we then copy them on positions
3719        later in an FRC game. This means WB might not recognize castlings with
3720        Rooks that have moved back to their original position as illegal,
3721        but in ICS mode that is not its job anyway.
3722     */
3723     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3724     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3725
3726         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3727             if(board[0][i] == WhiteRook) j = i;
3728         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3729         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3730             if(board[0][i] == WhiteRook) j = i;
3731         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3732         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3733             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3734         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3735         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3736             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3737         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3738
3739         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3740         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3741             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3742         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3743             if(board[BOARD_HEIGHT-1][k] == bKing)
3744                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3745         if(gameInfo.variant == VariantTwoKings) {
3746             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3747             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3748             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3749         }
3750     } else { int r;
3751         r = boards[moveNum][CASTLING][0] = initialRights[0];
3752         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3753         r = boards[moveNum][CASTLING][1] = initialRights[1];
3754         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3755         r = boards[moveNum][CASTLING][3] = initialRights[3];
3756         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3757         r = boards[moveNum][CASTLING][4] = initialRights[4];
3758         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3759         /* wildcastle kludge: always assume King has rights */
3760         r = boards[moveNum][CASTLING][2] = initialRights[2];
3761         r = boards[moveNum][CASTLING][5] = initialRights[5];
3762     }
3763     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3764     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3765
3766     
3767     if (ics_getting_history == H_GOT_REQ_HEADER ||
3768         ics_getting_history == H_GOT_UNREQ_HEADER) {
3769         /* This was an initial position from a move list, not
3770            the current position */
3771         return;
3772     }
3773     
3774     /* Update currentMove and known move number limits */
3775     newMove = newGame || moveNum > forwardMostMove;
3776
3777     if (newGame) {
3778         forwardMostMove = backwardMostMove = currentMove = moveNum;
3779         if (gameMode == IcsExamining && moveNum == 0) {
3780           /* Workaround for ICS limitation: we are not told the wild
3781              type when starting to examine a game.  But if we ask for
3782              the move list, the move list header will tell us */
3783             ics_getting_history = H_REQUESTED;
3784             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3785             SendToICS(str);
3786         }
3787     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3788                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3789 #if ZIPPY
3790         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3791         /* [HGM] applied this also to an engine that is silently watching        */
3792         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3793             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3794             gameInfo.variant == currentlyInitializedVariant) {
3795           takeback = forwardMostMove - moveNum;
3796           for (i = 0; i < takeback; i++) {
3797             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3798             SendToProgram("undo\n", &first);
3799           }
3800         }
3801 #endif
3802
3803         forwardMostMove = moveNum;
3804         if (!pausing || currentMove > forwardMostMove)
3805           currentMove = forwardMostMove;
3806     } else {
3807         /* New part of history that is not contiguous with old part */ 
3808         if (pausing && gameMode == IcsExamining) {
3809             pauseExamInvalid = TRUE;
3810             forwardMostMove = pauseExamForwardMostMove;
3811             return;
3812         }
3813         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3814 #if ZIPPY
3815             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3816                 // [HGM] when we will receive the move list we now request, it will be
3817                 // fed to the engine from the first move on. So if the engine is not
3818                 // in the initial position now, bring it there.
3819                 InitChessProgram(&first, 0);
3820             }
3821 #endif
3822             ics_getting_history = H_REQUESTED;
3823             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3824             SendToICS(str);
3825         }
3826         forwardMostMove = backwardMostMove = currentMove = moveNum;
3827     }
3828     
3829     /* Update the clocks */
3830     if (strchr(elapsed_time, '.')) {
3831       /* Time is in ms */
3832       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3833       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3834     } else {
3835       /* Time is in seconds */
3836       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3837       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3838     }
3839       
3840
3841 #if ZIPPY
3842     if (appData.zippyPlay && newGame &&
3843         gameMode != IcsObserving && gameMode != IcsIdle &&
3844         gameMode != IcsExamining)
3845       ZippyFirstBoard(moveNum, basetime, increment);
3846 #endif
3847     
3848     /* Put the move on the move list, first converting
3849        to canonical algebraic form. */
3850     if (moveNum > 0) {
3851   if (appData.debugMode) {
3852     if (appData.debugMode) { int f = forwardMostMove;
3853         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3854                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3855                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3856     }
3857     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3858     fprintf(debugFP, "moveNum = %d\n", moveNum);
3859     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3860     setbuf(debugFP, NULL);
3861   }
3862         if (moveNum <= backwardMostMove) {
3863             /* We don't know what the board looked like before
3864                this move.  Punt. */
3865             strcpy(parseList[moveNum - 1], move_str);
3866             strcat(parseList[moveNum - 1], " ");
3867             strcat(parseList[moveNum - 1], elapsed_time);
3868             moveList[moveNum - 1][0] = NULLCHAR;
3869         } else if (strcmp(move_str, "none") == 0) {
3870             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3871             /* Again, we don't know what the board looked like;
3872                this is really the start of the game. */
3873             parseList[moveNum - 1][0] = NULLCHAR;
3874             moveList[moveNum - 1][0] = NULLCHAR;
3875             backwardMostMove = moveNum;
3876             startedFromSetupPosition = TRUE;
3877             fromX = fromY = toX = toY = -1;
3878         } else {
3879           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3880           //                 So we parse the long-algebraic move string in stead of the SAN move
3881           int valid; char buf[MSG_SIZ], *prom;
3882
3883           // str looks something like "Q/a1-a2"; kill the slash
3884           if(str[1] == '/') 
3885                 sprintf(buf, "%c%s", str[0], str+2);
3886           else  strcpy(buf, str); // might be castling
3887           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3888                 strcat(buf, prom); // long move lacks promo specification!
3889           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3890                 if(appData.debugMode) 
3891                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3892                 strcpy(move_str, buf);
3893           }
3894           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3895                                 &fromX, &fromY, &toX, &toY, &promoChar)
3896                || ParseOneMove(buf, moveNum - 1, &moveType,
3897                                 &fromX, &fromY, &toX, &toY, &promoChar);
3898           // end of long SAN patch
3899           if (valid) {
3900             (void) CoordsToAlgebraic(boards[moveNum - 1],
3901                                      PosFlags(moveNum - 1),
3902                                      fromY, fromX, toY, toX, promoChar,
3903                                      parseList[moveNum-1]);
3904             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3905               case MT_NONE:
3906               case MT_STALEMATE:
3907               default:
3908                 break;
3909               case MT_CHECK:
3910                 if(gameInfo.variant != VariantShogi)
3911                     strcat(parseList[moveNum - 1], "+");
3912                 break;
3913               case MT_CHECKMATE:
3914               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3915                 strcat(parseList[moveNum - 1], "#");
3916                 break;
3917             }
3918             strcat(parseList[moveNum - 1], " ");
3919             strcat(parseList[moveNum - 1], elapsed_time);
3920             /* currentMoveString is set as a side-effect of ParseOneMove */
3921             strcpy(moveList[moveNum - 1], currentMoveString);
3922             strcat(moveList[moveNum - 1], "\n");
3923           } else {
3924             /* Move from ICS was illegal!?  Punt. */
3925   if (appData.debugMode) {
3926     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3927     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3928   }
3929             strcpy(parseList[moveNum - 1], move_str);
3930             strcat(parseList[moveNum - 1], " ");
3931             strcat(parseList[moveNum - 1], elapsed_time);
3932             moveList[moveNum - 1][0] = NULLCHAR;
3933             fromX = fromY = toX = toY = -1;
3934           }
3935         }
3936   if (appData.debugMode) {
3937     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3938     setbuf(debugFP, NULL);
3939   }
3940
3941 #if ZIPPY
3942         /* Send move to chess program (BEFORE animating it). */
3943         if (appData.zippyPlay && !newGame && newMove && 
3944            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3945
3946             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3947                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3948                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3949                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3950                             move_str);
3951                     DisplayError(str, 0);
3952                 } else {
3953                     if (first.sendTime) {
3954                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3955                     }
3956                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3957                     if (firstMove && !bookHit) {
3958                         firstMove = FALSE;
3959                         if (first.useColors) {
3960                           SendToProgram(gameMode == IcsPlayingWhite ?
3961                                         "white\ngo\n" :
3962                                         "black\ngo\n", &first);
3963                         } else {
3964                           SendToProgram("go\n", &first);
3965                         }
3966                         first.maybeThinking = TRUE;
3967                     }
3968                 }
3969             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3970               if (moveList[moveNum - 1][0] == NULLCHAR) {
3971                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3972                 DisplayError(str, 0);
3973               } else {
3974                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3975                 SendMoveToProgram(moveNum - 1, &first);
3976               }
3977             }
3978         }
3979 #endif
3980     }
3981
3982     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3983         /* If move comes from a remote source, animate it.  If it
3984            isn't remote, it will have already been animated. */
3985         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3986             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3987         }
3988         if (!pausing && appData.highlightLastMove) {
3989             SetHighlights(fromX, fromY, toX, toY);
3990         }
3991     }
3992     
3993     /* Start the clocks */
3994     whiteFlag = blackFlag = FALSE;
3995     appData.clockMode = !(basetime == 0 && increment == 0);
3996     if (ticking == 0) {
3997       ics_clock_paused = TRUE;
3998       StopClocks();
3999     } else if (ticking == 1) {
4000       ics_clock_paused = FALSE;
4001     }
4002     if (gameMode == IcsIdle ||
4003         relation == RELATION_OBSERVING_STATIC ||
4004         relation == RELATION_EXAMINING ||
4005         ics_clock_paused)
4006       DisplayBothClocks();
4007     else
4008       StartClocks();
4009     
4010     /* Display opponents and material strengths */
4011     if (gameInfo.variant != VariantBughouse &&
4012         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4013         if (tinyLayout || smallLayout) {
4014             if(gameInfo.variant == VariantNormal)
4015                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4016                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4017                     basetime, increment);
4018             else
4019                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4020                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4021                     basetime, increment, (int) gameInfo.variant);
4022         } else {
4023             if(gameInfo.variant == VariantNormal)
4024                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4025                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4026                     basetime, increment);
4027             else
4028                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4029                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4030                     basetime, increment, VariantName(gameInfo.variant));
4031         }
4032         DisplayTitle(str);
4033   if (appData.debugMode) {
4034     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4035   }
4036     }
4037
4038    
4039     /* Display the board */
4040     if (!pausing && !appData.noGUI) {
4041       
4042       if (appData.premove)
4043           if (!gotPremove || 
4044              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4045              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4046               ClearPremoveHighlights();
4047
4048       DrawPosition(FALSE, boards[currentMove]);
4049       DisplayMove(moveNum - 1);
4050       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4051             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4052               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4053         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4054       }
4055     }
4056
4057     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4058 #if ZIPPY
4059     if(bookHit) { // [HGM] book: simulate book reply
4060         static char bookMove[MSG_SIZ]; // a bit generous?
4061
4062         programStats.nodes = programStats.depth = programStats.time = 
4063         programStats.score = programStats.got_only_move = 0;
4064         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4065
4066         strcpy(bookMove, "move ");
4067         strcat(bookMove, bookHit);
4068         HandleMachineMove(bookMove, &first);
4069     }
4070 #endif
4071 }
4072
4073 void
4074 GetMoveListEvent()
4075 {
4076     char buf[MSG_SIZ];
4077     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4078         ics_getting_history = H_REQUESTED;
4079         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4080         SendToICS(buf);
4081     }
4082 }
4083
4084 void
4085 AnalysisPeriodicEvent(force)
4086      int force;
4087 {
4088     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4089          && !force) || !appData.periodicUpdates)
4090       return;
4091
4092     /* Send . command to Crafty to collect stats */
4093     SendToProgram(".\n", &first);
4094
4095     /* Don't send another until we get a response (this makes
4096        us stop sending to old Crafty's which don't understand
4097        the "." command (sending illegal cmds resets node count & time,
4098        which looks bad)) */
4099     programStats.ok_to_send = 0;
4100 }
4101
4102 void ics_update_width(new_width)
4103         int new_width;
4104 {
4105         ics_printf("set width %d\n", new_width);
4106 }
4107
4108 void
4109 SendMoveToProgram(moveNum, cps)
4110      int moveNum;
4111      ChessProgramState *cps;
4112 {
4113     char buf[MSG_SIZ];
4114
4115     if (cps->useUsermove) {
4116       SendToProgram("usermove ", cps);
4117     }
4118     if (cps->useSAN) {
4119       char *space;
4120       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4121         int len = space - parseList[moveNum];
4122         memcpy(buf, parseList[moveNum], len);
4123         buf[len++] = '\n';
4124         buf[len] = NULLCHAR;
4125       } else {
4126         sprintf(buf, "%s\n", parseList[moveNum]);
4127       }
4128       SendToProgram(buf, cps);
4129     } else {
4130       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4131         AlphaRank(moveList[moveNum], 4);
4132         SendToProgram(moveList[moveNum], cps);
4133         AlphaRank(moveList[moveNum], 4); // and back
4134       } else
4135       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4136        * the engine. It would be nice to have a better way to identify castle 
4137        * moves here. */
4138       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4139                                                                          && cps->useOOCastle) {
4140         int fromX = moveList[moveNum][0] - AAA; 
4141         int fromY = moveList[moveNum][1] - ONE;
4142         int toX = moveList[moveNum][2] - AAA; 
4143         int toY = moveList[moveNum][3] - ONE;
4144         if((boards[moveNum][fromY][fromX] == WhiteKing 
4145             && boards[moveNum][toY][toX] == WhiteRook)
4146            || (boards[moveNum][fromY][fromX] == BlackKing 
4147                && boards[moveNum][toY][toX] == BlackRook)) {
4148           if(toX > fromX) SendToProgram("O-O\n", cps);
4149           else SendToProgram("O-O-O\n", cps);
4150         }
4151         else SendToProgram(moveList[moveNum], cps);
4152       }
4153       else SendToProgram(moveList[moveNum], cps);
4154       /* End of additions by Tord */
4155     }
4156
4157     /* [HGM] setting up the opening has brought engine in force mode! */
4158     /*       Send 'go' if we are in a mode where machine should play. */
4159     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4160         (gameMode == TwoMachinesPlay   ||
4161 #ifdef ZIPPY
4162          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4163 #endif
4164          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4165         SendToProgram("go\n", cps);
4166   if (appData.debugMode) {
4167     fprintf(debugFP, "(extra)\n");
4168   }
4169     }
4170     setboardSpoiledMachineBlack = 0;
4171 }
4172
4173 void
4174 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4175      ChessMove moveType;
4176      int fromX, fromY, toX, toY;
4177 {
4178     char user_move[MSG_SIZ];
4179
4180     switch (moveType) {
4181       default:
4182         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4183                 (int)moveType, fromX, fromY, toX, toY);
4184         DisplayError(user_move + strlen("say "), 0);
4185         break;
4186       case WhiteKingSideCastle:
4187       case BlackKingSideCastle:
4188       case WhiteQueenSideCastleWild:
4189       case BlackQueenSideCastleWild:
4190       /* PUSH Fabien */
4191       case WhiteHSideCastleFR:
4192       case BlackHSideCastleFR:
4193       /* POP Fabien */
4194         sprintf(user_move, "o-o\n");
4195         break;
4196       case WhiteQueenSideCastle:
4197       case BlackQueenSideCastle:
4198       case WhiteKingSideCastleWild:
4199       case BlackKingSideCastleWild:
4200       /* PUSH Fabien */
4201       case WhiteASideCastleFR:
4202       case BlackASideCastleFR:
4203       /* POP Fabien */
4204         sprintf(user_move, "o-o-o\n");
4205         break;
4206       case WhitePromotionQueen:
4207       case BlackPromotionQueen:
4208       case WhitePromotionRook:
4209       case BlackPromotionRook:
4210       case WhitePromotionBishop:
4211       case BlackPromotionBishop:
4212       case WhitePromotionKnight:
4213       case BlackPromotionKnight:
4214       case WhitePromotionKing:
4215       case BlackPromotionKing:
4216       case WhitePromotionChancellor:
4217       case BlackPromotionChancellor:
4218       case WhitePromotionArchbishop:
4219       case BlackPromotionArchbishop:
4220         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4221             sprintf(user_move, "%c%c%c%c=%c\n",
4222                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4223                 PieceToChar(WhiteFerz));
4224         else if(gameInfo.variant == VariantGreat)
4225             sprintf(user_move, "%c%c%c%c=%c\n",
4226                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4227                 PieceToChar(WhiteMan));
4228         else
4229             sprintf(user_move, "%c%c%c%c=%c\n",
4230                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4231                 PieceToChar(PromoPiece(moveType)));
4232         break;
4233       case WhiteDrop:
4234       case BlackDrop:
4235         sprintf(user_move, "%c@%c%c\n",
4236                 ToUpper(PieceToChar((ChessSquare) fromX)),
4237                 AAA + toX, ONE + toY);
4238         break;
4239       case NormalMove:
4240       case WhiteCapturesEnPassant:
4241       case BlackCapturesEnPassant:
4242       case IllegalMove:  /* could be a variant we don't quite understand */
4243         sprintf(user_move, "%c%c%c%c\n",
4244                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4245         break;
4246     }
4247     SendToICS(user_move);
4248     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4249         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4250 }
4251
4252 void
4253 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4254      int rf, ff, rt, ft;
4255      char promoChar;
4256      char move[7];
4257 {
4258     if (rf == DROP_RANK) {
4259         sprintf(move, "%c@%c%c\n",
4260                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4261     } else {
4262         if (promoChar == 'x' || promoChar == NULLCHAR) {
4263             sprintf(move, "%c%c%c%c\n",
4264                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4265         } else {
4266             sprintf(move, "%c%c%c%c%c\n",
4267                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4268         }
4269     }
4270 }
4271
4272 void
4273 ProcessICSInitScript(f)
4274      FILE *f;
4275 {
4276     char buf[MSG_SIZ];
4277
4278     while (fgets(buf, MSG_SIZ, f)) {
4279         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4280     }
4281
4282     fclose(f);
4283 }
4284
4285
4286 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4287 void
4288 AlphaRank(char *move, int n)
4289 {
4290 //    char *p = move, c; int x, y;
4291
4292     if (appData.debugMode) {
4293         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4294     }
4295
4296     if(move[1]=='*' && 
4297        move[2]>='0' && move[2]<='9' &&
4298        move[3]>='a' && move[3]<='x'    ) {
4299         move[1] = '@';
4300         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4301         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4302     } else
4303     if(move[0]>='0' && move[0]<='9' &&
4304        move[1]>='a' && move[1]<='x' &&
4305        move[2]>='0' && move[2]<='9' &&
4306        move[3]>='a' && move[3]<='x'    ) {
4307         /* input move, Shogi -> normal */
4308         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4309         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4310         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4311         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4312     } else
4313     if(move[1]=='@' &&
4314        move[3]>='0' && move[3]<='9' &&
4315        move[2]>='a' && move[2]<='x'    ) {
4316         move[1] = '*';
4317         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4318         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4319     } else
4320     if(
4321        move[0]>='a' && move[0]<='x' &&
4322        move[3]>='0' && move[3]<='9' &&
4323        move[2]>='a' && move[2]<='x'    ) {
4324          /* output move, normal -> Shogi */
4325         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4326         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4327         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4328         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4329         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4330     }
4331     if (appData.debugMode) {
4332         fprintf(debugFP, "   out = '%s'\n", move);
4333     }
4334 }
4335
4336 /* Parser for moves from gnuchess, ICS, or user typein box */
4337 Boolean
4338 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4339      char *move;
4340      int moveNum;
4341      ChessMove *moveType;
4342      int *fromX, *fromY, *toX, *toY;
4343      char *promoChar;
4344 {       
4345     if (appData.debugMode) {
4346         fprintf(debugFP, "move to parse: %s\n", move);
4347     }
4348     *moveType = yylexstr(moveNum, move);
4349
4350     switch (*moveType) {
4351       case WhitePromotionChancellor:
4352       case BlackPromotionChancellor:
4353       case WhitePromotionArchbishop:
4354       case BlackPromotionArchbishop:
4355       case WhitePromotionQueen:
4356       case BlackPromotionQueen:
4357       case WhitePromotionRook:
4358       case BlackPromotionRook:
4359       case WhitePromotionBishop:
4360       case BlackPromotionBishop:
4361       case WhitePromotionKnight:
4362       case BlackPromotionKnight:
4363       case WhitePromotionKing:
4364       case BlackPromotionKing:
4365       case NormalMove:
4366       case WhiteCapturesEnPassant:
4367       case BlackCapturesEnPassant:
4368       case WhiteKingSideCastle:
4369       case WhiteQueenSideCastle:
4370       case BlackKingSideCastle:
4371       case BlackQueenSideCastle:
4372       case WhiteKingSideCastleWild:
4373       case WhiteQueenSideCastleWild:
4374       case BlackKingSideCastleWild:
4375       case BlackQueenSideCastleWild:
4376       /* Code added by Tord: */
4377       case WhiteHSideCastleFR:
4378       case WhiteASideCastleFR:
4379       case BlackHSideCastleFR:
4380       case BlackASideCastleFR:
4381       /* End of code added by Tord */
4382       case IllegalMove:         /* bug or odd chess variant */
4383         *fromX = currentMoveString[0] - AAA;
4384         *fromY = currentMoveString[1] - ONE;
4385         *toX = currentMoveString[2] - AAA;
4386         *toY = currentMoveString[3] - ONE;
4387         *promoChar = currentMoveString[4];
4388         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4389             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4390     if (appData.debugMode) {
4391         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4392     }
4393             *fromX = *fromY = *toX = *toY = 0;
4394             return FALSE;
4395         }
4396         if (appData.testLegality) {
4397           return (*moveType != IllegalMove);
4398         } else {
4399           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4400                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4401         }
4402
4403       case WhiteDrop:
4404       case BlackDrop:
4405         *fromX = *moveType == WhiteDrop ?
4406           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4407           (int) CharToPiece(ToLower(currentMoveString[0]));
4408         *fromY = DROP_RANK;
4409         *toX = currentMoveString[2] - AAA;
4410         *toY = currentMoveString[3] - ONE;
4411         *promoChar = NULLCHAR;
4412         return TRUE;
4413
4414       case AmbiguousMove:
4415       case ImpossibleMove:
4416       case (ChessMove) 0:       /* end of file */
4417       case ElapsedTime:
4418       case Comment:
4419       case PGNTag:
4420       case NAG:
4421       case WhiteWins:
4422       case BlackWins:
4423       case GameIsDrawn:
4424       default:
4425     if (appData.debugMode) {
4426         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4427     }
4428         /* bug? */
4429         *fromX = *fromY = *toX = *toY = 0;
4430         *promoChar = NULLCHAR;
4431         return FALSE;
4432     }
4433 }
4434
4435
4436 void
4437 ParsePV(char *pv)
4438 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4439   int fromX, fromY, toX, toY; char promoChar;
4440   ChessMove moveType;
4441   Boolean valid;
4442   int nr = 0;
4443
4444   endPV = forwardMostMove;
4445   do {
4446     while(*pv == ' ') pv++;
4447     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4448     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4449 if(appData.debugMode){
4450 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4451 }
4452     if(!valid && nr == 0 &&
4453        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4454         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4455     }
4456     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4457     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4458     nr++;
4459     if(endPV+1 > framePtr) break; // no space, truncate
4460     if(!valid) break;
4461     endPV++;
4462     CopyBoard(boards[endPV], boards[endPV-1]);
4463     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4464     moveList[endPV-1][0] = fromX + AAA;
4465     moveList[endPV-1][1] = fromY + ONE;
4466     moveList[endPV-1][2] = toX + AAA;
4467     moveList[endPV-1][3] = toY + ONE;
4468     parseList[endPV-1][0] = NULLCHAR;
4469   } while(valid);
4470   currentMove = endPV;
4471   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4472   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4473                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4474   DrawPosition(TRUE, boards[currentMove]);
4475 }
4476
4477 static int lastX, lastY;
4478
4479 Boolean
4480 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4481 {
4482         int startPV;
4483
4484         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4485         lastX = x; lastY = y;
4486         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4487         startPV = index;
4488       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4489       index = startPV;
4490         while(buf[index] && buf[index] != '\n') index++;
4491         buf[index] = 0;
4492         ParsePV(buf+startPV);
4493         *start = startPV; *end = index-1;
4494         return TRUE;
4495 }
4496
4497 Boolean
4498 LoadPV(int x, int y)
4499 { // called on right mouse click to load PV
4500   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4501   lastX = x; lastY = y;
4502   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4503   return TRUE;
4504 }
4505
4506 void
4507 UnLoadPV()
4508 {
4509   if(endPV < 0) return;
4510   endPV = -1;
4511   currentMove = forwardMostMove;
4512   ClearPremoveHighlights();
4513   DrawPosition(TRUE, boards[currentMove]);
4514 }
4515
4516 void
4517 MovePV(int x, int y, int h)
4518 { // step through PV based on mouse coordinates (called on mouse move)
4519   int margin = h>>3, step = 0;
4520
4521   if(endPV < 0) return;
4522   // we must somehow check if right button is still down (might be released off board!)
4523   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4524   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4525   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4526   if(!step) return;
4527   lastX = x; lastY = y;
4528   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4529   currentMove += step;
4530   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4531   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4532                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4533   DrawPosition(FALSE, boards[currentMove]);
4534 }
4535
4536
4537 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4538 // All positions will have equal probability, but the current method will not provide a unique
4539 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4540 #define DARK 1
4541 #define LITE 2
4542 #define ANY 3
4543
4544 int squaresLeft[4];
4545 int piecesLeft[(int)BlackPawn];
4546 int seed, nrOfShuffles;
4547
4548 void GetPositionNumber()
4549 {       // sets global variable seed
4550         int i;
4551
4552         seed = appData.defaultFrcPosition;
4553         if(seed < 0) { // randomize based on time for negative FRC position numbers
4554                 for(i=0; i<50; i++) seed += random();
4555                 seed = random() ^ random() >> 8 ^ random() << 8;
4556                 if(seed<0) seed = -seed;
4557         }
4558 }
4559
4560 int put(Board board, int pieceType, int rank, int n, int shade)
4561 // put the piece on the (n-1)-th empty squares of the given shade
4562 {
4563         int i;
4564
4565         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4566                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4567                         board[rank][i] = (ChessSquare) pieceType;
4568                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4569                         squaresLeft[ANY]--;
4570                         piecesLeft[pieceType]--; 
4571                         return i;
4572                 }
4573         }
4574         return -1;
4575 }
4576
4577
4578 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4579 // calculate where the next piece goes, (any empty square), and put it there
4580 {
4581         int i;
4582
4583         i = seed % squaresLeft[shade];
4584         nrOfShuffles *= squaresLeft[shade];
4585         seed /= squaresLeft[shade];
4586         put(board, pieceType, rank, i, shade);
4587 }
4588
4589 void AddTwoPieces(Board board, int pieceType, int rank)
4590 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4591 {
4592         int i, n=squaresLeft[ANY], j=n-1, k;
4593
4594         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4595         i = seed % k;  // pick one
4596         nrOfShuffles *= k;
4597         seed /= k;
4598         while(i >= j) i -= j--;
4599         j = n - 1 - j; i += j;
4600         put(board, pieceType, rank, j, ANY);
4601         put(board, pieceType, rank, i, ANY);
4602 }
4603
4604 void SetUpShuffle(Board board, int number)
4605 {
4606         int i, p, first=1;
4607
4608         GetPositionNumber(); nrOfShuffles = 1;
4609
4610         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4611         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4612         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4613
4614         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4615
4616         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4617             p = (int) board[0][i];
4618             if(p < (int) BlackPawn) piecesLeft[p] ++;
4619             board[0][i] = EmptySquare;
4620         }
4621
4622         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4623             // shuffles restricted to allow normal castling put KRR first
4624             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4625                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4626             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4627                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4628             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4629                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4630             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4631                 put(board, WhiteRook, 0, 0, ANY);
4632             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4633         }
4634
4635         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4636             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4637             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4638                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4639                 while(piecesLeft[p] >= 2) {
4640                     AddOnePiece(board, p, 0, LITE);
4641                     AddOnePiece(board, p, 0, DARK);
4642                 }
4643                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4644             }
4645
4646         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4647             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4648             // but we leave King and Rooks for last, to possibly obey FRC restriction
4649             if(p == (int)WhiteRook) continue;
4650             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4651             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4652         }
4653
4654         // now everything is placed, except perhaps King (Unicorn) and Rooks
4655
4656         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4657             // Last King gets castling rights
4658             while(piecesLeft[(int)WhiteUnicorn]) {
4659                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4660                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4661             }
4662
4663             while(piecesLeft[(int)WhiteKing]) {
4664                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4665                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4666             }
4667
4668
4669         } else {
4670             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4671             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4672         }
4673
4674         // Only Rooks can be left; simply place them all
4675         while(piecesLeft[(int)WhiteRook]) {
4676                 i = put(board, WhiteRook, 0, 0, ANY);
4677                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4678                         if(first) {
4679                                 first=0;
4680                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4681                         }
4682                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4683                 }
4684         }
4685         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4686             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4687         }
4688
4689         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4690 }
4691
4692 int SetCharTable( char *table, const char * map )
4693 /* [HGM] moved here from winboard.c because of its general usefulness */
4694 /*       Basically a safe strcpy that uses the last character as King */
4695 {
4696     int result = FALSE; int NrPieces;
4697
4698     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4699                     && NrPieces >= 12 && !(NrPieces&1)) {
4700         int i; /* [HGM] Accept even length from 12 to 34 */
4701
4702         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4703         for( i=0; i<NrPieces/2-1; i++ ) {
4704             table[i] = map[i];
4705             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4706         }
4707         table[(int) WhiteKing]  = map[NrPieces/2-1];
4708         table[(int) BlackKing]  = map[NrPieces-1];
4709
4710         result = TRUE;
4711     }
4712
4713     return result;
4714 }
4715
4716 void Prelude(Board board)
4717 {       // [HGM] superchess: random selection of exo-pieces
4718         int i, j, k; ChessSquare p; 
4719         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4720
4721         GetPositionNumber(); // use FRC position number
4722
4723         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4724             SetCharTable(pieceToChar, appData.pieceToCharTable);
4725             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4726                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4727         }
4728
4729         j = seed%4;                 seed /= 4; 
4730         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4731         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4732         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4733         j = seed%3 + (seed%3 >= j); seed /= 3; 
4734         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4735         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4736         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4737         j = seed%3;                 seed /= 3; 
4738         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4739         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4740         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4741         j = seed%2 + (seed%2 >= j); seed /= 2; 
4742         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4743         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4744         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4745         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4746         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4747         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4748         put(board, exoPieces[0],    0, 0, ANY);
4749         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4750 }
4751
4752 void
4753 InitPosition(redraw)
4754      int redraw;
4755 {
4756     ChessSquare (* pieces)[BOARD_FILES];
4757     int i, j, pawnRow, overrule,
4758     oldx = gameInfo.boardWidth,
4759     oldy = gameInfo.boardHeight,
4760     oldh = gameInfo.holdingsWidth,
4761     oldv = gameInfo.variant;
4762
4763     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4764
4765     /* [AS] Initialize pv info list [HGM] and game status */
4766     {
4767         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4768             pvInfoList[i].depth = 0;
4769             boards[i][EP_STATUS] = EP_NONE;
4770             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4771         }
4772
4773         initialRulePlies = 0; /* 50-move counter start */
4774
4775         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4776         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4777     }
4778
4779     
4780     /* [HGM] logic here is completely changed. In stead of full positions */
4781     /* the initialized data only consist of the two backranks. The switch */
4782     /* selects which one we will use, which is than copied to the Board   */
4783     /* initialPosition, which for the rest is initialized by Pawns and    */
4784     /* empty squares. This initial position is then copied to boards[0],  */
4785     /* possibly after shuffling, so that it remains available.            */
4786
4787     gameInfo.holdingsWidth = 0; /* default board sizes */
4788     gameInfo.boardWidth    = 8;
4789     gameInfo.boardHeight   = 8;
4790     gameInfo.holdingsSize  = 0;
4791     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4792     for(i=0; i<BOARD_FILES-2; i++)
4793       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4794     initialPosition[EP_STATUS] = EP_NONE;
4795     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4796
4797     switch (gameInfo.variant) {
4798     case VariantFischeRandom:
4799       shuffleOpenings = TRUE;
4800     default:
4801       pieces = FIDEArray;
4802       break;
4803     case VariantShatranj:
4804       pieces = ShatranjArray;
4805       nrCastlingRights = 0;
4806       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4807       break;
4808     case VariantMakruk:
4809       pieces = makrukArray;
4810       nrCastlingRights = 0;
4811       startedFromSetupPosition = TRUE;
4812       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4813       break;
4814     case VariantTwoKings:
4815       pieces = twoKingsArray;
4816       break;
4817     case VariantCapaRandom:
4818       shuffleOpenings = TRUE;
4819     case VariantCapablanca:
4820       pieces = CapablancaArray;
4821       gameInfo.boardWidth = 10;
4822       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4823       break;
4824     case VariantGothic:
4825       pieces = GothicArray;
4826       gameInfo.boardWidth = 10;
4827       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4828       break;
4829     case VariantJanus:
4830       pieces = JanusArray;
4831       gameInfo.boardWidth = 10;
4832       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4833       nrCastlingRights = 6;
4834         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4835         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4836         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4837         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4838         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4839         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4840       break;
4841     case VariantFalcon:
4842       pieces = FalconArray;
4843       gameInfo.boardWidth = 10;
4844       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4845       break;
4846     case VariantXiangqi:
4847       pieces = XiangqiArray;
4848       gameInfo.boardWidth  = 9;
4849       gameInfo.boardHeight = 10;
4850       nrCastlingRights = 0;
4851       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4852       break;
4853     case VariantShogi:
4854       pieces = ShogiArray;
4855       gameInfo.boardWidth  = 9;
4856       gameInfo.boardHeight = 9;
4857       gameInfo.holdingsSize = 7;
4858       nrCastlingRights = 0;
4859       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4860       break;
4861     case VariantCourier:
4862       pieces = CourierArray;
4863       gameInfo.boardWidth  = 12;
4864       nrCastlingRights = 0;
4865       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4866       break;
4867     case VariantKnightmate:
4868       pieces = KnightmateArray;
4869       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4870       break;
4871     case VariantFairy:
4872       pieces = fairyArray;
4873       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4874       break;
4875     case VariantGreat:
4876       pieces = GreatArray;
4877       gameInfo.boardWidth = 10;
4878       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4879       gameInfo.holdingsSize = 8;
4880       break;
4881     case VariantSuper:
4882       pieces = FIDEArray;
4883       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4884       gameInfo.holdingsSize = 8;
4885       startedFromSetupPosition = TRUE;
4886       break;
4887     case VariantCrazyhouse:
4888     case VariantBughouse:
4889       pieces = FIDEArray;
4890       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4891       gameInfo.holdingsSize = 5;
4892       break;
4893     case VariantWildCastle:
4894       pieces = FIDEArray;
4895       /* !!?shuffle with kings guaranteed to be on d or e file */
4896       shuffleOpenings = 1;
4897       break;
4898     case VariantNoCastle:
4899       pieces = FIDEArray;
4900       nrCastlingRights = 0;
4901       /* !!?unconstrained back-rank shuffle */
4902       shuffleOpenings = 1;
4903       break;
4904     }
4905
4906     overrule = 0;
4907     if(appData.NrFiles >= 0) {
4908         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4909         gameInfo.boardWidth = appData.NrFiles;
4910     }
4911     if(appData.NrRanks >= 0) {
4912         gameInfo.boardHeight = appData.NrRanks;
4913     }
4914     if(appData.holdingsSize >= 0) {
4915         i = appData.holdingsSize;
4916         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4917         gameInfo.holdingsSize = i;
4918     }
4919     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4920     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4921         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4922
4923     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4924     if(pawnRow < 1) pawnRow = 1;
4925     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4926
4927     /* User pieceToChar list overrules defaults */
4928     if(appData.pieceToCharTable != NULL)
4929         SetCharTable(pieceToChar, appData.pieceToCharTable);
4930
4931     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4932
4933         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4934             s = (ChessSquare) 0; /* account holding counts in guard band */
4935         for( i=0; i<BOARD_HEIGHT; i++ )
4936             initialPosition[i][j] = s;
4937
4938         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4939         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4940         initialPosition[pawnRow][j] = WhitePawn;
4941         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4942         if(gameInfo.variant == VariantXiangqi) {
4943             if(j&1) {
4944                 initialPosition[pawnRow][j] = 
4945                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4946                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4947                    initialPosition[2][j] = WhiteCannon;
4948                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4949                 }
4950             }
4951         }
4952         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4953     }
4954     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4955
4956             j=BOARD_LEFT+1;
4957             initialPosition[1][j] = WhiteBishop;
4958             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4959             j=BOARD_RGHT-2;
4960             initialPosition[1][j] = WhiteRook;
4961             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4962     }
4963
4964     if( nrCastlingRights == -1) {
4965         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4966         /*       This sets default castling rights from none to normal corners   */
4967         /* Variants with other castling rights must set them themselves above    */
4968         nrCastlingRights = 6;
4969        
4970         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4971         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4972         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4973         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4974         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4975         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4976      }
4977
4978      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4979      if(gameInfo.variant == VariantGreat) { // promotion commoners
4980         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4981         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4982         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4983         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4984      }
4985   if (appData.debugMode) {
4986     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4987   }
4988     if(shuffleOpenings) {
4989         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4990         startedFromSetupPosition = TRUE;
4991     }
4992     if(startedFromPositionFile) {
4993       /* [HGM] loadPos: use PositionFile for every new game */
4994       CopyBoard(initialPosition, filePosition);
4995       for(i=0; i<nrCastlingRights; i++)
4996           initialRights[i] = filePosition[CASTLING][i];
4997       startedFromSetupPosition = TRUE;
4998     }
4999
5000     CopyBoard(boards[0], initialPosition);
5001
5002     if(oldx != gameInfo.boardWidth ||
5003        oldy != gameInfo.boardHeight ||
5004        oldh != gameInfo.holdingsWidth
5005 #ifdef GOTHIC
5006        || oldv == VariantGothic ||        // For licensing popups
5007        gameInfo.variant == VariantGothic
5008 #endif
5009 #ifdef FALCON
5010        || oldv == VariantFalcon ||
5011        gameInfo.variant == VariantFalcon
5012 #endif
5013                                          )
5014             InitDrawingSizes(-2 ,0);
5015
5016     if (redraw)
5017       DrawPosition(TRUE, boards[currentMove]);
5018 }
5019
5020 void
5021 SendBoard(cps, moveNum)
5022      ChessProgramState *cps;
5023      int moveNum;
5024 {
5025     char message[MSG_SIZ];
5026     
5027     if (cps->useSetboard) {
5028       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5029       sprintf(message, "setboard %s\n", fen);
5030       SendToProgram(message, cps);
5031       free(fen);
5032
5033     } else {
5034       ChessSquare *bp;
5035       int i, j;
5036       /* Kludge to set black to move, avoiding the troublesome and now
5037        * deprecated "black" command.
5038        */
5039       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5040
5041       SendToProgram("edit\n", cps);
5042       SendToProgram("#\n", cps);
5043       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5044         bp = &boards[moveNum][i][BOARD_LEFT];
5045         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5046           if ((int) *bp < (int) BlackPawn) {
5047             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5048                     AAA + j, ONE + i);
5049             if(message[0] == '+' || message[0] == '~') {
5050                 sprintf(message, "%c%c%c+\n",
5051                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5052                         AAA + j, ONE + i);
5053             }
5054             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5055                 message[1] = BOARD_RGHT   - 1 - j + '1';
5056                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5057             }
5058             SendToProgram(message, cps);
5059           }
5060         }
5061       }
5062     
5063       SendToProgram("c\n", cps);
5064       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5065         bp = &boards[moveNum][i][BOARD_LEFT];
5066         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5067           if (((int) *bp != (int) EmptySquare)
5068               && ((int) *bp >= (int) BlackPawn)) {
5069             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5070                     AAA + j, ONE + i);
5071             if(message[0] == '+' || message[0] == '~') {
5072                 sprintf(message, "%c%c%c+\n",
5073                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5074                         AAA + j, ONE + i);
5075             }
5076             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5077                 message[1] = BOARD_RGHT   - 1 - j + '1';
5078                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5079             }
5080             SendToProgram(message, cps);
5081           }
5082         }
5083       }
5084     
5085       SendToProgram(".\n", cps);
5086     }
5087     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5088 }
5089
5090 int
5091 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5092 {
5093     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5094     /* [HGM] add Shogi promotions */
5095     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5096     ChessSquare piece;
5097     ChessMove moveType;
5098     Boolean premove;
5099
5100     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5101     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5102
5103     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5104       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5105         return FALSE;
5106
5107     piece = boards[currentMove][fromY][fromX];
5108     if(gameInfo.variant == VariantShogi) {
5109         promotionZoneSize = 3;
5110         highestPromotingPiece = (int)WhiteFerz;
5111     } else if(gameInfo.variant == VariantMakruk) {
5112         promotionZoneSize = 3;
5113     }
5114
5115     // next weed out all moves that do not touch the promotion zone at all
5116     if((int)piece >= BlackPawn) {
5117         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5118              return FALSE;
5119         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5120     } else {
5121         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5122            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5123     }
5124
5125     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5126
5127     // weed out mandatory Shogi promotions
5128     if(gameInfo.variant == VariantShogi) {
5129         if(piece >= BlackPawn) {
5130             if(toY == 0 && piece == BlackPawn ||
5131                toY == 0 && piece == BlackQueen ||
5132                toY <= 1 && piece == BlackKnight) {
5133                 *promoChoice = '+';
5134                 return FALSE;
5135             }
5136         } else {
5137             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5138                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5139                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5140                 *promoChoice = '+';
5141                 return FALSE;
5142             }
5143         }
5144     }
5145
5146     // weed out obviously illegal Pawn moves
5147     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5148         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5149         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5150         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5151         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5152         // note we are not allowed to test for valid (non-)capture, due to premove
5153     }
5154
5155     // we either have a choice what to promote to, or (in Shogi) whether to promote
5156     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5157         *promoChoice = PieceToChar(BlackFerz);  // no choice
5158         return FALSE;
5159     }
5160     if(appData.alwaysPromoteToQueen) { // predetermined
5161         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5162              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5163         else *promoChoice = PieceToChar(BlackQueen);
5164         return FALSE;
5165     }
5166
5167     // suppress promotion popup on illegal moves that are not premoves
5168     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5169               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5170     if(appData.testLegality && !premove) {
5171         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5172                         fromY, fromX, toY, toX, NULLCHAR);
5173         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5174            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5175             return FALSE;
5176     }
5177
5178     return TRUE;
5179 }
5180
5181 int
5182 InPalace(row, column)
5183      int row, column;
5184 {   /* [HGM] for Xiangqi */
5185     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5186          column < (BOARD_WIDTH + 4)/2 &&
5187          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5188     return FALSE;
5189 }
5190
5191 int
5192 PieceForSquare (x, y)
5193      int x;
5194      int y;
5195 {
5196   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5197      return -1;
5198   else
5199      return boards[currentMove][y][x];
5200 }
5201
5202 int
5203 OKToStartUserMove(x, y)
5204      int x, y;
5205 {
5206     ChessSquare from_piece;
5207     int white_piece;
5208
5209     if (matchMode) return FALSE;
5210     if (gameMode == EditPosition) return TRUE;
5211
5212     if (x >= 0 && y >= 0)
5213       from_piece = boards[currentMove][y][x];
5214     else
5215       from_piece = EmptySquare;
5216
5217     if (from_piece == EmptySquare) return FALSE;
5218
5219     white_piece = (int)from_piece >= (int)WhitePawn &&
5220       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5221
5222     switch (gameMode) {
5223       case PlayFromGameFile:
5224       case AnalyzeFile:
5225       case TwoMachinesPlay:
5226       case EndOfGame:
5227         return FALSE;
5228
5229       case IcsObserving:
5230       case IcsIdle:
5231         return FALSE;
5232
5233       case MachinePlaysWhite:
5234       case IcsPlayingBlack:
5235         if (appData.zippyPlay) return FALSE;
5236         if (white_piece) {
5237             DisplayMoveError(_("You are playing Black"));
5238             return FALSE;
5239         }
5240         break;
5241
5242       case MachinePlaysBlack:
5243       case IcsPlayingWhite:
5244         if (appData.zippyPlay) return FALSE;
5245         if (!white_piece) {
5246             DisplayMoveError(_("You are playing White"));
5247             return FALSE;
5248         }
5249         break;
5250
5251       case EditGame:
5252         if (!white_piece && WhiteOnMove(currentMove)) {
5253             DisplayMoveError(_("It is White's turn"));
5254             return FALSE;
5255         }           
5256         if (white_piece && !WhiteOnMove(currentMove)) {
5257             DisplayMoveError(_("It is Black's turn"));
5258             return FALSE;
5259         }           
5260         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5261             /* Editing correspondence game history */
5262             /* Could disallow this or prompt for confirmation */
5263             cmailOldMove = -1;
5264         }
5265         break;
5266
5267       case BeginningOfGame:
5268         if (appData.icsActive) return FALSE;
5269         if (!appData.noChessProgram) {
5270             if (!white_piece) {
5271                 DisplayMoveError(_("You are playing White"));
5272                 return FALSE;
5273             }
5274         }
5275         break;
5276         
5277       case Training:
5278         if (!white_piece && WhiteOnMove(currentMove)) {
5279             DisplayMoveError(_("It is White's turn"));
5280             return FALSE;
5281         }           
5282         if (white_piece && !WhiteOnMove(currentMove)) {
5283             DisplayMoveError(_("It is Black's turn"));
5284             return FALSE;
5285         }           
5286         break;
5287
5288       default:
5289       case IcsExamining:
5290         break;
5291     }
5292     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5293         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5294         && gameMode != AnalyzeFile && gameMode != Training) {
5295         DisplayMoveError(_("Displayed position is not current"));
5296         return FALSE;
5297     }
5298     return TRUE;
5299 }
5300
5301 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5302 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5303 int lastLoadGameUseList = FALSE;
5304 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5305 ChessMove lastLoadGameStart = (ChessMove) 0;
5306
5307 ChessMove
5308 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5309      int fromX, fromY, toX, toY;
5310      int promoChar;
5311      Boolean captureOwn;
5312 {
5313     ChessMove moveType;
5314     ChessSquare pdown, pup;
5315
5316     /* Check if the user is playing in turn.  This is complicated because we
5317        let the user "pick up" a piece before it is his turn.  So the piece he
5318        tried to pick up may have been captured by the time he puts it down!
5319        Therefore we use the color the user is supposed to be playing in this
5320        test, not the color of the piece that is currently on the starting
5321        square---except in EditGame mode, where the user is playing both
5322        sides; fortunately there the capture race can't happen.  (It can
5323        now happen in IcsExamining mode, but that's just too bad.  The user
5324        will get a somewhat confusing message in that case.)
5325        */
5326
5327     switch (gameMode) {
5328       case PlayFromGameFile:
5329       case AnalyzeFile:
5330       case TwoMachinesPlay:
5331       case EndOfGame:
5332       case IcsObserving:
5333       case IcsIdle:
5334         /* We switched into a game mode where moves are not accepted,
5335            perhaps while the mouse button was down. */
5336         return ImpossibleMove;
5337
5338       case MachinePlaysWhite:
5339         /* User is moving for Black */
5340         if (WhiteOnMove(currentMove)) {
5341             DisplayMoveError(_("It is White's turn"));
5342             return ImpossibleMove;
5343         }
5344         break;
5345
5346       case MachinePlaysBlack:
5347         /* User is moving for White */
5348         if (!WhiteOnMove(currentMove)) {
5349             DisplayMoveError(_("It is Black's turn"));
5350             return ImpossibleMove;
5351         }
5352         break;
5353
5354       case EditGame:
5355       case IcsExamining:
5356       case BeginningOfGame:
5357       case AnalyzeMode:
5358       case Training:
5359         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5360             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5361             /* User is moving for Black */
5362             if (WhiteOnMove(currentMove)) {
5363                 DisplayMoveError(_("It is White's turn"));
5364                 return ImpossibleMove;
5365             }
5366         } else {
5367             /* User is moving for White */
5368             if (!WhiteOnMove(currentMove)) {
5369                 DisplayMoveError(_("It is Black's turn"));
5370                 return ImpossibleMove;
5371             }
5372         }
5373         break;
5374
5375       case IcsPlayingBlack:
5376         /* User is moving for Black */
5377         if (WhiteOnMove(currentMove)) {
5378             if (!appData.premove) {
5379                 DisplayMoveError(_("It is White's turn"));
5380             } else if (toX >= 0 && toY >= 0) {
5381                 premoveToX = toX;
5382                 premoveToY = toY;
5383                 premoveFromX = fromX;
5384                 premoveFromY = fromY;
5385                 premovePromoChar = promoChar;
5386                 gotPremove = 1;
5387                 if (appData.debugMode) 
5388                     fprintf(debugFP, "Got premove: fromX %d,"
5389                             "fromY %d, toX %d, toY %d\n",
5390                             fromX, fromY, toX, toY);
5391             }
5392             return ImpossibleMove;
5393         }
5394         break;
5395
5396       case IcsPlayingWhite:
5397         /* User is moving for White */
5398         if (!WhiteOnMove(currentMove)) {
5399             if (!appData.premove) {
5400                 DisplayMoveError(_("It is Black's turn"));
5401             } else if (toX >= 0 && toY >= 0) {
5402                 premoveToX = toX;
5403                 premoveToY = toY;
5404                 premoveFromX = fromX;
5405                 premoveFromY = fromY;
5406                 premovePromoChar = promoChar;
5407                 gotPremove = 1;
5408                 if (appData.debugMode) 
5409                     fprintf(debugFP, "Got premove: fromX %d,"
5410                             "fromY %d, toX %d, toY %d\n",
5411                             fromX, fromY, toX, toY);
5412             }
5413             return ImpossibleMove;
5414         }
5415         break;
5416
5417       default:
5418         break;
5419
5420       case EditPosition:
5421         /* EditPosition, empty square, or different color piece;
5422            click-click move is possible */
5423         if (toX == -2 || toY == -2) {
5424             boards[0][fromY][fromX] = EmptySquare;
5425             return AmbiguousMove;
5426         } else if (toX >= 0 && toY >= 0) {
5427             boards[0][toY][toX] = boards[0][fromY][fromX];
5428             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5429                 if(boards[0][fromY][0] != EmptySquare) {
5430                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5431                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5432                 }
5433             } else
5434             if(fromX == BOARD_RGHT+1) {
5435                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5436                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5437                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5438                 }
5439             } else
5440             boards[0][fromY][fromX] = EmptySquare;
5441             return AmbiguousMove;
5442         }
5443         return ImpossibleMove;
5444     }
5445
5446     if(toX < 0 || toY < 0) return ImpossibleMove;
5447     pdown = boards[currentMove][fromY][fromX];
5448     pup = boards[currentMove][toY][toX];
5449
5450     /* [HGM] If move started in holdings, it means a drop */
5451     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5452          if( pup != EmptySquare ) return ImpossibleMove;
5453          if(appData.testLegality) {
5454              /* it would be more logical if LegalityTest() also figured out
5455               * which drops are legal. For now we forbid pawns on back rank.
5456               * Shogi is on its own here...
5457               */
5458              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5459                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5460                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5461          }
5462          return WhiteDrop; /* Not needed to specify white or black yet */
5463     }
5464
5465     /* [HGM] always test for legality, to get promotion info */
5466     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5467                                          fromY, fromX, toY, toX, promoChar);
5468     /* [HGM] but possibly ignore an IllegalMove result */
5469     if (appData.testLegality) {
5470         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5471             DisplayMoveError(_("Illegal move"));
5472             return ImpossibleMove;
5473         }
5474     }
5475
5476     return moveType;
5477     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5478        function is made into one that returns an OK move type if FinishMove
5479        should be called. This to give the calling driver routine the
5480        opportunity to finish the userMove input with a promotion popup,
5481        without bothering the user with this for invalid or illegal moves */
5482
5483 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5484 }
5485
5486 /* Common tail of UserMoveEvent and DropMenuEvent */
5487 int
5488 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5489      ChessMove moveType;
5490      int fromX, fromY, toX, toY;
5491      /*char*/int promoChar;
5492 {
5493     char *bookHit = 0;
5494
5495     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5496         // [HGM] superchess: suppress promotions to non-available piece
5497         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5498         if(WhiteOnMove(currentMove)) {
5499             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5500         } else {
5501             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5502         }
5503     }
5504
5505     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5506        move type in caller when we know the move is a legal promotion */
5507     if(moveType == NormalMove && promoChar)
5508         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5509
5510     /* [HGM] convert drag-and-drop piece drops to standard form */
5511     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5512          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5513            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5514                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5515            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5516            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5517            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5518            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5519          fromY = DROP_RANK;
5520     }
5521
5522     /* [HGM] <popupFix> The following if has been moved here from
5523        UserMoveEvent(). Because it seemed to belong here (why not allow
5524        piece drops in training games?), and because it can only be
5525        performed after it is known to what we promote. */
5526     if (gameMode == Training) {
5527       /* compare the move played on the board to the next move in the
5528        * game. If they match, display the move and the opponent's response. 
5529        * If they don't match, display an error message.
5530        */
5531       int saveAnimate;
5532       Board testBoard;
5533       CopyBoard(testBoard, boards[currentMove]);
5534       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5535
5536       if (CompareBoards(testBoard, boards[currentMove+1])) {
5537         ForwardInner(currentMove+1);
5538
5539         /* Autoplay the opponent's response.
5540          * if appData.animate was TRUE when Training mode was entered,
5541          * the response will be animated.
5542          */
5543         saveAnimate = appData.animate;
5544         appData.animate = animateTraining;
5545         ForwardInner(currentMove+1);
5546         appData.animate = saveAnimate;
5547
5548         /* check for the end of the game */
5549         if (currentMove >= forwardMostMove) {
5550           gameMode = PlayFromGameFile;
5551           ModeHighlight();
5552           SetTrainingModeOff();
5553           DisplayInformation(_("End of game"));
5554         }
5555       } else {
5556         DisplayError(_("Incorrect move"), 0);
5557       }
5558       return 1;
5559     }
5560
5561   /* Ok, now we know that the move is good, so we can kill
5562      the previous line in Analysis Mode */
5563   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5564                                 && currentMove < forwardMostMove) {
5565     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5566   }
5567
5568   /* If we need the chess program but it's dead, restart it */
5569   ResurrectChessProgram();
5570
5571   /* A user move restarts a paused game*/
5572   if (pausing)
5573     PauseEvent();
5574
5575   thinkOutput[0] = NULLCHAR;
5576
5577   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5578
5579   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5580
5581   if (gameMode == BeginningOfGame) {
5582     if (appData.noChessProgram) {
5583       gameMode = EditGame;
5584       SetGameInfo();
5585     } else {
5586       char buf[MSG_SIZ];
5587       gameMode = MachinePlaysBlack;
5588       StartClocks();
5589       SetGameInfo();
5590       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5591       DisplayTitle(buf);
5592       if (first.sendName) {
5593         sprintf(buf, "name %s\n", gameInfo.white);
5594         SendToProgram(buf, &first);
5595       }
5596       StartClocks();
5597     }
5598     ModeHighlight();
5599   }
5600
5601   /* Relay move to ICS or chess engine */
5602   if (appData.icsActive) {
5603     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5604         gameMode == IcsExamining) {
5605       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5606         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5607         SendToICS("draw ");
5608         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5609       }
5610       // also send plain move, in case ICS does not understand atomic claims
5611       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5612       ics_user_moved = 1;
5613     }
5614   } else {
5615     if (first.sendTime && (gameMode == BeginningOfGame ||
5616                            gameMode == MachinePlaysWhite ||
5617                            gameMode == MachinePlaysBlack)) {
5618       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5619     }
5620     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5621          // [HGM] book: if program might be playing, let it use book
5622         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5623         first.maybeThinking = TRUE;
5624     } else SendMoveToProgram(forwardMostMove-1, &first);
5625     if (currentMove == cmailOldMove + 1) {
5626       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5627     }
5628   }
5629
5630   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5631
5632   switch (gameMode) {
5633   case EditGame:
5634     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5635     case MT_NONE:
5636     case MT_CHECK:
5637       break;
5638     case MT_CHECKMATE:
5639     case MT_STAINMATE:
5640       if (WhiteOnMove(currentMove)) {
5641         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5642       } else {
5643         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5644       }
5645       break;
5646     case MT_STALEMATE:
5647       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5648       break;
5649     }
5650     break;
5651     
5652   case MachinePlaysBlack:
5653   case MachinePlaysWhite:
5654     /* disable certain menu options while machine is thinking */
5655     SetMachineThinkingEnables();
5656     break;
5657
5658   default:
5659     break;
5660   }
5661
5662   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5663         
5664   if(bookHit) { // [HGM] book: simulate book reply
5665         static char bookMove[MSG_SIZ]; // a bit generous?
5666
5667         programStats.nodes = programStats.depth = programStats.time = 
5668         programStats.score = programStats.got_only_move = 0;
5669         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5670
5671         strcpy(bookMove, "move ");
5672         strcat(bookMove, bookHit);
5673         HandleMachineMove(bookMove, &first);
5674   }
5675   return 1;
5676 }
5677
5678 void
5679 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5680      int fromX, fromY, toX, toY;
5681      int promoChar;
5682 {
5683     /* [HGM] This routine was added to allow calling of its two logical
5684        parts from other modules in the old way. Before, UserMoveEvent()
5685        automatically called FinishMove() if the move was OK, and returned
5686        otherwise. I separated the two, in order to make it possible to
5687        slip a promotion popup in between. But that it always needs two
5688        calls, to the first part, (now called UserMoveTest() ), and to
5689        FinishMove if the first part succeeded. Calls that do not need
5690        to do anything in between, can call this routine the old way. 
5691     */
5692     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5693 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5694     if(moveType == AmbiguousMove)
5695         DrawPosition(FALSE, boards[currentMove]);
5696     else if(moveType != ImpossibleMove && moveType != Comment)
5697         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5698 }
5699
5700 void
5701 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5702      Board board;
5703      int flags;
5704      ChessMove kind;
5705      int rf, ff, rt, ft;
5706      VOIDSTAR closure;
5707 {
5708     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5709     Markers *m = (Markers *) closure;
5710     if(rf == fromY && ff == fromX)
5711         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5712                          || kind == WhiteCapturesEnPassant
5713                          || kind == BlackCapturesEnPassant);
5714     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5715 }
5716
5717 void
5718 MarkTargetSquares(int clear)
5719 {
5720   int x, y;
5721   if(!appData.markers || !appData.highlightDragging || 
5722      !appData.testLegality || gameMode == EditPosition) return;
5723   if(clear) {
5724     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5725   } else {
5726     int capt = 0;
5727     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5728     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5729       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5730       if(capt)
5731       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5732     }
5733   }
5734   DrawPosition(TRUE, NULL);
5735 }
5736
5737 void LeftClick(ClickType clickType, int xPix, int yPix)
5738 {
5739     int x, y;
5740     Boolean saveAnimate;
5741     static int second = 0, promotionChoice = 0;
5742     char promoChoice = NULLCHAR;
5743
5744     if (clickType == Press) ErrorPopDown();
5745     MarkTargetSquares(1);
5746
5747     x = EventToSquare(xPix, BOARD_WIDTH);
5748     y = EventToSquare(yPix, BOARD_HEIGHT);
5749     if (!flipView && y >= 0) {
5750         y = BOARD_HEIGHT - 1 - y;
5751     }
5752     if (flipView && x >= 0) {
5753         x = BOARD_WIDTH - 1 - x;
5754     }
5755
5756     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5757         if(clickType == Release) return; // ignore upclick of click-click destination
5758         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5759         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5760         if(gameInfo.holdingsWidth && 
5761                 (WhiteOnMove(currentMove) 
5762                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5763                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5764             // click in right holdings, for determining promotion piece
5765             ChessSquare p = boards[currentMove][y][x];
5766             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5767             if(p != EmptySquare) {
5768                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5769                 fromX = fromY = -1;
5770                 return;
5771             }
5772         }
5773         DrawPosition(FALSE, boards[currentMove]);
5774         return;
5775     }
5776
5777     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5778     if(clickType == Press
5779             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5780               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5781               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5782         return;
5783
5784     if (fromX == -1) {
5785         if (clickType == Press) {
5786             /* First square */
5787             if (OKToStartUserMove(x, y)) {
5788                 fromX = x;
5789                 fromY = y;
5790                 second = 0;
5791                 MarkTargetSquares(0);
5792                 DragPieceBegin(xPix, yPix);
5793                 if (appData.highlightDragging) {
5794                     SetHighlights(x, y, -1, -1);
5795                 }
5796             }
5797         }
5798         return;
5799     }
5800
5801     /* fromX != -1 */
5802     if (clickType == Press && gameMode != EditPosition) {
5803         ChessSquare fromP;
5804         ChessSquare toP;
5805         int frc;
5806
5807         // ignore off-board to clicks
5808         if(y < 0 || x < 0) return;
5809
5810         /* Check if clicking again on the same color piece */
5811         fromP = boards[currentMove][fromY][fromX];
5812         toP = boards[currentMove][y][x];
5813         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5814         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5815              WhitePawn <= toP && toP <= WhiteKing &&
5816              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5817              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5818             (BlackPawn <= fromP && fromP <= BlackKing && 
5819              BlackPawn <= toP && toP <= BlackKing &&
5820              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5821              !(fromP == BlackKing && toP == BlackRook && frc))) {
5822             /* Clicked again on same color piece -- changed his mind */
5823             second = (x == fromX && y == fromY);
5824             if (appData.highlightDragging) {
5825                 SetHighlights(x, y, -1, -1);
5826             } else {
5827                 ClearHighlights();
5828             }
5829             if (OKToStartUserMove(x, y)) {
5830                 fromX = x;
5831                 fromY = y;
5832                 MarkTargetSquares(0);
5833                 DragPieceBegin(xPix, yPix);
5834             }
5835             return;
5836         }
5837         // ignore clicks on holdings
5838         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5839     }
5840
5841     if (clickType == Release && x == fromX && y == fromY) {
5842         DragPieceEnd(xPix, yPix);
5843         if (appData.animateDragging) {
5844             /* Undo animation damage if any */
5845             DrawPosition(FALSE, NULL);
5846         }
5847         if (second) {
5848             /* Second up/down in same square; just abort move */
5849             second = 0;
5850             fromX = fromY = -1;
5851             ClearHighlights();
5852             gotPremove = 0;
5853             ClearPremoveHighlights();
5854         } else {
5855             /* First upclick in same square; start click-click mode */
5856             SetHighlights(x, y, -1, -1);
5857         }
5858         return;
5859     }
5860
5861     /* we now have a different from- and (possibly off-board) to-square */
5862     /* Completed move */
5863     toX = x;
5864     toY = y;
5865     saveAnimate = appData.animate;
5866     if (clickType == Press) {
5867         /* Finish clickclick move */
5868         if (appData.animate || appData.highlightLastMove) {
5869             SetHighlights(fromX, fromY, toX, toY);
5870         } else {
5871             ClearHighlights();
5872         }
5873     } else {
5874         /* Finish drag move */
5875         if (appData.highlightLastMove) {
5876             SetHighlights(fromX, fromY, toX, toY);
5877         } else {
5878             ClearHighlights();
5879         }
5880         DragPieceEnd(xPix, yPix);
5881         /* Don't animate move and drag both */
5882         appData.animate = FALSE;
5883     }
5884
5885     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5886     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5887         ChessSquare piece = boards[currentMove][fromY][fromX];
5888         if(gameMode == EditPosition && piece != EmptySquare &&
5889            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5890             int n;
5891              
5892             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5893                 n = PieceToNumber(piece - (int)BlackPawn);
5894                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5895                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5896                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5897             } else
5898             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5899                 n = PieceToNumber(piece);
5900                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5901                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5902                 boards[currentMove][n][BOARD_WIDTH-2]++;
5903             }
5904             boards[currentMove][fromY][fromX] = EmptySquare;
5905         }
5906         ClearHighlights();
5907         fromX = fromY = -1;
5908         DrawPosition(TRUE, boards[currentMove]);
5909         return;
5910     }
5911
5912     // off-board moves should not be highlighted
5913     if(x < 0 || x < 0) ClearHighlights();
5914
5915     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5916         SetHighlights(fromX, fromY, toX, toY);
5917         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5918             // [HGM] super: promotion to captured piece selected from holdings
5919             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5920             promotionChoice = TRUE;
5921             // kludge follows to temporarily execute move on display, without promoting yet
5922             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5923             boards[currentMove][toY][toX] = p;
5924             DrawPosition(FALSE, boards[currentMove]);
5925             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5926             boards[currentMove][toY][toX] = q;
5927             DisplayMessage("Click in holdings to choose piece", "");
5928             return;
5929         }
5930         PromotionPopUp();
5931     } else {
5932         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5933         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5934         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5935         fromX = fromY = -1;
5936     }
5937     appData.animate = saveAnimate;
5938     if (appData.animate || appData.animateDragging) {
5939         /* Undo animation damage if needed */
5940         DrawPosition(FALSE, NULL);
5941     }
5942 }
5943
5944 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
5945 {   // front-end-free part taken out of PieceMenuPopup
5946     int whichMenu;
5947
5948     if (action == Release) UnLoadPV(); // [HGM] pv
5949     if (action != Press) return -2;
5950     switch (gameMode) {
5951       case EditPosition:
5952       case IcsExamining:
5953         whichMenu = 0;
5954         break;
5955       case IcsObserving:
5956         if(!appData.icsEngineAnalyze) return -1;
5957       case IcsPlayingWhite:
5958       case IcsPlayingBlack:
5959         if(!appData.zippyPlay) goto noZip;
5960       case AnalyzeMode:
5961       case AnalyzeFile:
5962       case MachinePlaysWhite:
5963       case MachinePlaysBlack:
5964       case TwoMachinesPlay: // [HGM] pv: use for showing PV
5965         if (!appData.dropMenu) {
5966           LoadPV(x, y);
5967           return -1;
5968         }
5969         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
5970            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
5971       case EditGame:
5972       noZip:
5973         if (!appData.dropMenu || appData.testLegality &&
5974             gameInfo.variant != VariantBughouse &&
5975             gameInfo.variant != VariantCrazyhouse) return -1;
5976         whichMenu = 1;
5977         break;
5978       default:
5979         return -1;
5980     }
5981
5982     if (((*fromX = EventToSquare(x, BOARD_WIDTH)) < 0) ||
5983         ((*fromY = EventToSquare(y, BOARD_HEIGHT)) < 0)) {
5984         *fromX = *fromY = -1;
5985         return -1;
5986     }
5987     if (flipView)
5988       *fromX = BOARD_WIDTH - 1 - *fromX;
5989     else
5990       *fromY = BOARD_HEIGHT - 1 - *fromY;
5991
5992     return whichMenu;
5993 }
5994
5995 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5996 {
5997 //    char * hint = lastHint;
5998     FrontEndProgramStats stats;
5999
6000     stats.which = cps == &first ? 0 : 1;
6001     stats.depth = cpstats->depth;
6002     stats.nodes = cpstats->nodes;
6003     stats.score = cpstats->score;
6004     stats.time = cpstats->time;
6005     stats.pv = cpstats->movelist;
6006     stats.hint = lastHint;
6007     stats.an_move_index = 0;
6008     stats.an_move_count = 0;
6009
6010     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6011         stats.hint = cpstats->move_name;
6012         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6013         stats.an_move_count = cpstats->nr_moves;
6014     }
6015
6016     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6017
6018     SetProgramStats( &stats );
6019 }
6020
6021 int
6022 Adjudicate(ChessProgramState *cps)
6023 {       // [HGM] some adjudications useful with buggy engines
6024         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6025         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6026         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6027         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6028         int k, count = 0; static int bare = 1;
6029         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6030         Boolean canAdjudicate = !appData.icsActive;
6031
6032         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6033         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6034             if( appData.testLegality )
6035             {   /* [HGM] Some more adjudications for obstinate engines */
6036                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6037                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6038                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6039                 static int moveCount = 6;
6040                 ChessMove result;
6041                 char *reason = NULL;
6042
6043                 /* Count what is on board. */
6044                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6045                 {   ChessSquare p = boards[forwardMostMove][i][j];
6046                     int m=i;
6047
6048                     switch((int) p)
6049                     {   /* count B,N,R and other of each side */
6050                         case WhiteKing:
6051                         case BlackKing:
6052                              NrK++; break; // [HGM] atomic: count Kings
6053                         case WhiteKnight:
6054                              NrWN++; break;
6055                         case WhiteBishop:
6056                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6057                              bishopsColor |= 1 << ((i^j)&1);
6058                              NrWB++; break;
6059                         case BlackKnight:
6060                              NrBN++; break;
6061                         case BlackBishop:
6062                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6063                              bishopsColor |= 1 << ((i^j)&1);
6064                              NrBB++; break;
6065                         case WhiteRook:
6066                              NrWR++; break;
6067                         case BlackRook:
6068                              NrBR++; break;
6069                         case WhiteQueen:
6070                              NrWQ++; break;
6071                         case BlackQueen:
6072                              NrBQ++; break;
6073                         case EmptySquare: 
6074                              break;
6075                         case BlackPawn:
6076                              m = 7-i;
6077                         case WhitePawn:
6078                              PawnAdvance += m; NrPawns++;
6079                     }
6080                     NrPieces += (p != EmptySquare);
6081                     NrW += ((int)p < (int)BlackPawn);
6082                     if(gameInfo.variant == VariantXiangqi && 
6083                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6084                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6085                         NrW -= ((int)p < (int)BlackPawn);
6086                     }
6087                 }
6088
6089                 /* Some material-based adjudications that have to be made before stalemate test */
6090                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6091                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6092                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6093                      if(canAdjudicate && appData.checkMates) {
6094                          if(engineOpponent)
6095                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6096                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6097                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6098                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6099                          return 1;
6100                      }
6101                 }
6102
6103                 /* Bare King in Shatranj (loses) or Losers (wins) */
6104                 if( NrW == 1 || NrPieces - NrW == 1) {
6105                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6106                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6107                      if(canAdjudicate && appData.checkMates) {
6108                          if(engineOpponent)
6109                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6110                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6111                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6112                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6113                          return 1;
6114                      }
6115                   } else
6116                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6117                   {    /* bare King */
6118                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6119                         if(canAdjudicate && appData.checkMates) {
6120                             /* but only adjudicate if adjudication enabled */
6121                             if(engineOpponent)
6122                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6123                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6124                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6125                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6126                             return 1;
6127                         }
6128                   }
6129                 } else bare = 1;
6130
6131
6132             // don't wait for engine to announce game end if we can judge ourselves
6133             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6134               case MT_CHECK:
6135                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6136                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6137                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6138                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6139                             checkCnt++;
6140                         if(checkCnt >= 2) {
6141                             reason = "Xboard adjudication: 3rd check";
6142                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6143                             break;
6144                         }
6145                     }
6146                 }
6147               case MT_NONE:
6148               default:
6149                 break;
6150               case MT_STALEMATE:
6151               case MT_STAINMATE:
6152                 reason = "Xboard adjudication: Stalemate";
6153                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6154                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6155                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6156                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6157                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6158                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6159                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6160                                                                         EP_CHECKMATE : EP_WINS);
6161                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6162                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6163                 }
6164                 break;
6165               case MT_CHECKMATE:
6166                 reason = "Xboard adjudication: Checkmate";
6167                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6168                 break;
6169             }
6170
6171                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6172                     case EP_STALEMATE:
6173                         result = GameIsDrawn; break;
6174                     case EP_CHECKMATE:
6175                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6176                     case EP_WINS:
6177                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6178                     default:
6179                         result = (ChessMove) 0;
6180                 }
6181                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6182                     if(engineOpponent)
6183                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6184                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6185                     GameEnds( result, reason, GE_XBOARD );
6186                     return 1;
6187                 }
6188
6189                 /* Next absolutely insufficient mating material. */
6190                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6191                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6192                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6193                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6194                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6195
6196                      /* always flag draws, for judging claims */
6197                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6198
6199                      if(canAdjudicate && appData.materialDraws) {
6200                          /* but only adjudicate them if adjudication enabled */
6201                          if(engineOpponent) {
6202                            SendToProgram("force\n", engineOpponent); // suppress reply
6203                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6204                          }
6205                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6206                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6207                          return 1;
6208                      }
6209                 }
6210
6211                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6212                 if(NrPieces == 4 && 
6213                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6214                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6215                    || NrWN==2 || NrBN==2     /* KNNK */
6216                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6217                   ) ) {
6218                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6219                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6220                           if(engineOpponent) {
6221                             SendToProgram("force\n", engineOpponent); // suppress reply
6222                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6223                           }
6224                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6225                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6226                           return 1;
6227                      }
6228                 } else moveCount = 6;
6229             }
6230         }
6231           
6232         if (appData.debugMode) { int i;
6233             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6234                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6235                     appData.drawRepeats);
6236             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6237               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6238             
6239         }
6240
6241         // Repetition draws and 50-move rule can be applied independently of legality testing
6242
6243                 /* Check for rep-draws */
6244                 count = 0;
6245                 for(k = forwardMostMove-2;
6246                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6247                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6248                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6249                     k-=2)
6250                 {   int rights=0;
6251                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6252                         /* compare castling rights */
6253                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6254                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6255                                 rights++; /* King lost rights, while rook still had them */
6256                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6257                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6258                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6259                                    rights++; /* but at least one rook lost them */
6260                         }
6261                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6262                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6263                                 rights++; 
6264                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6265                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6266                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6267                                    rights++;
6268                         }
6269                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6270                             && appData.drawRepeats > 1) {
6271                              /* adjudicate after user-specified nr of repeats */
6272                              if(engineOpponent) {
6273                                SendToProgram("force\n", engineOpponent); // suppress reply
6274                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6275                              }
6276                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6277                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6278                                 // [HGM] xiangqi: check for forbidden perpetuals
6279                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6280                                 for(m=forwardMostMove; m>k; m-=2) {
6281                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6282                                         ourPerpetual = 0; // the current mover did not always check
6283                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6284                                         hisPerpetual = 0; // the opponent did not always check
6285                                 }
6286                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6287                                                                         ourPerpetual, hisPerpetual);
6288                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6289                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6290                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6291                                     return 1;
6292                                 }
6293                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6294                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6295                                 // Now check for perpetual chases
6296                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6297                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6298                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6299                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6300                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6301                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6302                                         return 1;
6303                                     }
6304                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6305                                         break; // Abort repetition-checking loop.
6306                                 }
6307                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6308                              }
6309                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6310                              return 1;
6311                         }
6312                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6313                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6314                     }
6315                 }
6316
6317                 /* Now we test for 50-move draws. Determine ply count */
6318                 count = forwardMostMove;
6319                 /* look for last irreversble move */
6320                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6321                     count--;
6322                 /* if we hit starting position, add initial plies */
6323                 if( count == backwardMostMove )
6324                     count -= initialRulePlies;
6325                 count = forwardMostMove - count; 
6326                 if( count >= 100)
6327                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6328                          /* this is used to judge if draw claims are legal */
6329                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6330                          if(engineOpponent) {
6331                            SendToProgram("force\n", engineOpponent); // suppress reply
6332                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6333                          }
6334                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6335                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6336                          return 1;
6337                 }
6338
6339                 /* if draw offer is pending, treat it as a draw claim
6340                  * when draw condition present, to allow engines a way to
6341                  * claim draws before making their move to avoid a race
6342                  * condition occurring after their move
6343                  */
6344                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6345                          char *p = NULL;
6346                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6347                              p = "Draw claim: 50-move rule";
6348                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6349                              p = "Draw claim: 3-fold repetition";
6350                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6351                              p = "Draw claim: insufficient mating material";
6352                          if( p != NULL && canAdjudicate) {
6353                              if(engineOpponent) {
6354                                SendToProgram("force\n", engineOpponent); // suppress reply
6355                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6356                              }
6357                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6358                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6359                              return 1;
6360                          }
6361                 }
6362
6363                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6364                     if(engineOpponent) {
6365                       SendToProgram("force\n", engineOpponent); // suppress reply
6366                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6367                     }
6368                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6369                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6370                     return 1;
6371                 }
6372         return 0;
6373 }
6374
6375 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6376 {   // [HGM] book: this routine intercepts moves to simulate book replies
6377     char *bookHit = NULL;
6378
6379     //first determine if the incoming move brings opponent into his book
6380     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6381         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6382     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6383     if(bookHit != NULL && !cps->bookSuspend) {
6384         // make sure opponent is not going to reply after receiving move to book position
6385         SendToProgram("force\n", cps);
6386         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6387     }
6388     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6389     // now arrange restart after book miss
6390     if(bookHit) {
6391         // after a book hit we never send 'go', and the code after the call to this routine
6392         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6393         char buf[MSG_SIZ];
6394         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6395         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6396         SendToProgram(buf, cps);
6397         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6398     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6399         SendToProgram("go\n", cps);
6400         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6401     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6402         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6403             SendToProgram("go\n", cps); 
6404         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6405     }
6406     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6407 }
6408
6409 char *savedMessage;
6410 ChessProgramState *savedState;
6411 void DeferredBookMove(void)
6412 {
6413         if(savedState->lastPing != savedState->lastPong)
6414                     ScheduleDelayedEvent(DeferredBookMove, 10);
6415         else
6416         HandleMachineMove(savedMessage, savedState);
6417 }
6418
6419 void
6420 HandleMachineMove(message, cps)
6421      char *message;
6422      ChessProgramState *cps;
6423 {
6424     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6425     char realname[MSG_SIZ];
6426     int fromX, fromY, toX, toY;
6427     ChessMove moveType;
6428     char promoChar;
6429     char *p;
6430     int machineWhite;
6431     char *bookHit;
6432
6433     cps->userError = 0;
6434
6435 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6436     /*
6437      * Kludge to ignore BEL characters
6438      */
6439     while (*message == '\007') message++;
6440
6441     /*
6442      * [HGM] engine debug message: ignore lines starting with '#' character
6443      */
6444     if(cps->debug && *message == '#') return;
6445
6446     /*
6447      * Look for book output
6448      */
6449     if (cps == &first && bookRequested) {
6450         if (message[0] == '\t' || message[0] == ' ') {
6451             /* Part of the book output is here; append it */
6452             strcat(bookOutput, message);
6453             strcat(bookOutput, "  \n");
6454             return;
6455         } else if (bookOutput[0] != NULLCHAR) {
6456             /* All of book output has arrived; display it */
6457             char *p = bookOutput;
6458             while (*p != NULLCHAR) {
6459                 if (*p == '\t') *p = ' ';
6460                 p++;
6461             }
6462             DisplayInformation(bookOutput);
6463             bookRequested = FALSE;
6464             /* Fall through to parse the current output */
6465         }
6466     }
6467
6468     /*
6469      * Look for machine move.
6470      */
6471     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6472         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6473     {
6474         /* This method is only useful on engines that support ping */
6475         if (cps->lastPing != cps->lastPong) {
6476           if (gameMode == BeginningOfGame) {
6477             /* Extra move from before last new; ignore */
6478             if (appData.debugMode) {
6479                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6480             }
6481           } else {
6482             if (appData.debugMode) {
6483                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6484                         cps->which, gameMode);
6485             }
6486
6487             SendToProgram("undo\n", cps);
6488           }
6489           return;
6490         }
6491
6492         switch (gameMode) {
6493           case BeginningOfGame:
6494             /* Extra move from before last reset; ignore */
6495             if (appData.debugMode) {
6496                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6497             }
6498             return;
6499
6500           case EndOfGame:
6501           case IcsIdle:
6502           default:
6503             /* Extra move after we tried to stop.  The mode test is
6504                not a reliable way of detecting this problem, but it's
6505                the best we can do on engines that don't support ping.
6506             */
6507             if (appData.debugMode) {
6508                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6509                         cps->which, gameMode);
6510             }
6511             SendToProgram("undo\n", cps);
6512             return;
6513
6514           case MachinePlaysWhite:
6515           case IcsPlayingWhite:
6516             machineWhite = TRUE;
6517             break;
6518
6519           case MachinePlaysBlack:
6520           case IcsPlayingBlack:
6521             machineWhite = FALSE;
6522             break;
6523
6524           case TwoMachinesPlay:
6525             machineWhite = (cps->twoMachinesColor[0] == 'w');
6526             break;
6527         }
6528         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6529             if (appData.debugMode) {
6530                 fprintf(debugFP,
6531                         "Ignoring move out of turn by %s, gameMode %d"
6532                         ", forwardMost %d\n",
6533                         cps->which, gameMode, forwardMostMove);
6534             }
6535             return;
6536         }
6537
6538     if (appData.debugMode) { int f = forwardMostMove;
6539         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6540                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6541                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6542     }
6543         if(cps->alphaRank) AlphaRank(machineMove, 4);
6544         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6545                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6546             /* Machine move could not be parsed; ignore it. */
6547             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6548                     machineMove, cps->which);
6549             DisplayError(buf1, 0);
6550             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6551                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6552             if (gameMode == TwoMachinesPlay) {
6553               GameEnds(machineWhite ? BlackWins : WhiteWins,
6554                        buf1, GE_XBOARD);
6555             }
6556             return;
6557         }
6558
6559         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6560         /* So we have to redo legality test with true e.p. status here,  */
6561         /* to make sure an illegal e.p. capture does not slip through,   */
6562         /* to cause a forfeit on a justified illegal-move complaint      */
6563         /* of the opponent.                                              */
6564         if( gameMode==TwoMachinesPlay && appData.testLegality
6565             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6566                                                               ) {
6567            ChessMove moveType;
6568            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6569                              fromY, fromX, toY, toX, promoChar);
6570             if (appData.debugMode) {
6571                 int i;
6572                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6573                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6574                 fprintf(debugFP, "castling rights\n");
6575             }
6576             if(moveType == IllegalMove) {
6577                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6578                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6579                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6580                            buf1, GE_XBOARD);
6581                 return;
6582            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6583            /* [HGM] Kludge to handle engines that send FRC-style castling
6584               when they shouldn't (like TSCP-Gothic) */
6585            switch(moveType) {
6586              case WhiteASideCastleFR:
6587              case BlackASideCastleFR:
6588                toX+=2;
6589                currentMoveString[2]++;
6590                break;
6591              case WhiteHSideCastleFR:
6592              case BlackHSideCastleFR:
6593                toX--;
6594                currentMoveString[2]--;
6595                break;
6596              default: ; // nothing to do, but suppresses warning of pedantic compilers
6597            }
6598         }
6599         hintRequested = FALSE;
6600         lastHint[0] = NULLCHAR;
6601         bookRequested = FALSE;
6602         /* Program may be pondering now */
6603         cps->maybeThinking = TRUE;
6604         if (cps->sendTime == 2) cps->sendTime = 1;
6605         if (cps->offeredDraw) cps->offeredDraw--;
6606
6607         /* currentMoveString is set as a side-effect of ParseOneMove */
6608         strcpy(machineMove, currentMoveString);
6609         strcat(machineMove, "\n");
6610         strcpy(moveList[forwardMostMove], machineMove);
6611
6612         /* [AS] Save move info and clear stats for next move */
6613         pvInfoList[ forwardMostMove ].score = programStats.score;
6614         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6615         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6616         ClearProgramStats();
6617         thinkOutput[0] = NULLCHAR;
6618         hiddenThinkOutputState = 0;
6619
6620         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6621
6622         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6623         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6624             int count = 0;
6625
6626             while( count < adjudicateLossPlies ) {
6627                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6628
6629                 if( count & 1 ) {
6630                     score = -score; /* Flip score for winning side */
6631                 }
6632
6633                 if( score > adjudicateLossThreshold ) {
6634                     break;
6635                 }
6636
6637                 count++;
6638             }
6639
6640             if( count >= adjudicateLossPlies ) {
6641                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6642
6643                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6644                     "Xboard adjudication", 
6645                     GE_XBOARD );
6646
6647                 return;
6648             }
6649         }
6650
6651         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6652
6653 #if ZIPPY
6654         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6655             first.initDone) {
6656           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6657                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6658                 SendToICS("draw ");
6659                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6660           }
6661           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6662           ics_user_moved = 1;
6663           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6664                 char buf[3*MSG_SIZ];
6665
6666                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6667                         programStats.score / 100.,
6668                         programStats.depth,
6669                         programStats.time / 100.,
6670                         (unsigned int)programStats.nodes,
6671                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6672                         programStats.movelist);
6673                 SendToICS(buf);
6674 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6675           }
6676         }
6677 #endif
6678
6679         bookHit = NULL;
6680         if (gameMode == TwoMachinesPlay) {
6681             /* [HGM] relaying draw offers moved to after reception of move */
6682             /* and interpreting offer as claim if it brings draw condition */
6683             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6684                 SendToProgram("draw\n", cps->other);
6685             }
6686             if (cps->other->sendTime) {
6687                 SendTimeRemaining(cps->other,
6688                                   cps->other->twoMachinesColor[0] == 'w');
6689             }
6690             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6691             if (firstMove && !bookHit) {
6692                 firstMove = FALSE;
6693                 if (cps->other->useColors) {
6694                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6695                 }
6696                 SendToProgram("go\n", cps->other);
6697             }
6698             cps->other->maybeThinking = TRUE;
6699         }
6700
6701         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6702         
6703         if (!pausing && appData.ringBellAfterMoves) {
6704             RingBell();
6705         }
6706
6707         /* 
6708          * Reenable menu items that were disabled while
6709          * machine was thinking
6710          */
6711         if (gameMode != TwoMachinesPlay)
6712             SetUserThinkingEnables();
6713
6714         // [HGM] book: after book hit opponent has received move and is now in force mode
6715         // force the book reply into it, and then fake that it outputted this move by jumping
6716         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6717         if(bookHit) {
6718                 static char bookMove[MSG_SIZ]; // a bit generous?
6719
6720                 strcpy(bookMove, "move ");
6721                 strcat(bookMove, bookHit);
6722                 message = bookMove;
6723                 cps = cps->other;
6724                 programStats.nodes = programStats.depth = programStats.time = 
6725                 programStats.score = programStats.got_only_move = 0;
6726                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6727
6728                 if(cps->lastPing != cps->lastPong) {
6729                     savedMessage = message; // args for deferred call
6730                     savedState = cps;
6731                     ScheduleDelayedEvent(DeferredBookMove, 10);
6732                     return;
6733                 }
6734                 goto FakeBookMove;
6735         }
6736
6737         return;
6738     }
6739
6740     /* Set special modes for chess engines.  Later something general
6741      *  could be added here; for now there is just one kludge feature,
6742      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6743      *  when "xboard" is given as an interactive command.
6744      */
6745     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6746         cps->useSigint = FALSE;
6747         cps->useSigterm = FALSE;
6748     }
6749     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6750       ParseFeatures(message+8, cps);
6751       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6752     }
6753
6754     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6755      * want this, I was asked to put it in, and obliged.
6756      */
6757     if (!strncmp(message, "setboard ", 9)) {
6758         Board initial_position;
6759
6760         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6761
6762         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6763             DisplayError(_("Bad FEN received from engine"), 0);
6764             return ;
6765         } else {
6766            Reset(TRUE, FALSE);
6767            CopyBoard(boards[0], initial_position);
6768            initialRulePlies = FENrulePlies;
6769            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6770            else gameMode = MachinePlaysBlack;                 
6771            DrawPosition(FALSE, boards[currentMove]);
6772         }
6773         return;
6774     }
6775
6776     /*
6777      * Look for communication commands
6778      */
6779     if (!strncmp(message, "telluser ", 9)) {
6780         DisplayNote(message + 9);
6781         return;
6782     }
6783     if (!strncmp(message, "tellusererror ", 14)) {
6784         cps->userError = 1;
6785         DisplayError(message + 14, 0);
6786         return;
6787     }
6788     if (!strncmp(message, "tellopponent ", 13)) {
6789       if (appData.icsActive) {
6790         if (loggedOn) {
6791           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6792           SendToICS(buf1);
6793         }
6794       } else {
6795         DisplayNote(message + 13);
6796       }
6797       return;
6798     }
6799     if (!strncmp(message, "tellothers ", 11)) {
6800       if (appData.icsActive) {
6801         if (loggedOn) {
6802           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6803           SendToICS(buf1);
6804         }
6805       }
6806       return;
6807     }
6808     if (!strncmp(message, "tellall ", 8)) {
6809       if (appData.icsActive) {
6810         if (loggedOn) {
6811           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6812           SendToICS(buf1);
6813         }
6814       } else {
6815         DisplayNote(message + 8);
6816       }
6817       return;
6818     }
6819     if (strncmp(message, "warning", 7) == 0) {
6820         /* Undocumented feature, use tellusererror in new code */
6821         DisplayError(message, 0);
6822         return;
6823     }
6824     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6825         strcpy(realname, cps->tidy);
6826         strcat(realname, " query");
6827         AskQuestion(realname, buf2, buf1, cps->pr);
6828         return;
6829     }
6830     /* Commands from the engine directly to ICS.  We don't allow these to be 
6831      *  sent until we are logged on. Crafty kibitzes have been known to 
6832      *  interfere with the login process.
6833      */
6834     if (loggedOn) {
6835         if (!strncmp(message, "tellics ", 8)) {
6836             SendToICS(message + 8);
6837             SendToICS("\n");
6838             return;
6839         }
6840         if (!strncmp(message, "tellicsnoalias ", 15)) {
6841             SendToICS(ics_prefix);
6842             SendToICS(message + 15);
6843             SendToICS("\n");
6844             return;
6845         }
6846         /* The following are for backward compatibility only */
6847         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6848             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6849             SendToICS(ics_prefix);
6850             SendToICS(message);
6851             SendToICS("\n");
6852             return;
6853         }
6854     }
6855     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6856         return;
6857     }
6858     /*
6859      * If the move is illegal, cancel it and redraw the board.
6860      * Also deal with other error cases.  Matching is rather loose
6861      * here to accommodate engines written before the spec.
6862      */
6863     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6864         strncmp(message, "Error", 5) == 0) {
6865         if (StrStr(message, "name") || 
6866             StrStr(message, "rating") || StrStr(message, "?") ||
6867             StrStr(message, "result") || StrStr(message, "board") ||
6868             StrStr(message, "bk") || StrStr(message, "computer") ||
6869             StrStr(message, "variant") || StrStr(message, "hint") ||
6870             StrStr(message, "random") || StrStr(message, "depth") ||
6871             StrStr(message, "accepted")) {
6872             return;
6873         }
6874         if (StrStr(message, "protover")) {
6875           /* Program is responding to input, so it's apparently done
6876              initializing, and this error message indicates it is
6877              protocol version 1.  So we don't need to wait any longer
6878              for it to initialize and send feature commands. */
6879           FeatureDone(cps, 1);
6880           cps->protocolVersion = 1;
6881           return;
6882         }
6883         cps->maybeThinking = FALSE;
6884
6885         if (StrStr(message, "draw")) {
6886             /* Program doesn't have "draw" command */
6887             cps->sendDrawOffers = 0;
6888             return;
6889         }
6890         if (cps->sendTime != 1 &&
6891             (StrStr(message, "time") || StrStr(message, "otim"))) {
6892           /* Program apparently doesn't have "time" or "otim" command */
6893           cps->sendTime = 0;
6894           return;
6895         }
6896         if (StrStr(message, "analyze")) {
6897             cps->analysisSupport = FALSE;
6898             cps->analyzing = FALSE;
6899             Reset(FALSE, TRUE);
6900             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6901             DisplayError(buf2, 0);
6902             return;
6903         }
6904         if (StrStr(message, "(no matching move)st")) {
6905           /* Special kludge for GNU Chess 4 only */
6906           cps->stKludge = TRUE;
6907           SendTimeControl(cps, movesPerSession, timeControl,
6908                           timeIncrement, appData.searchDepth,
6909                           searchTime);
6910           return;
6911         }
6912         if (StrStr(message, "(no matching move)sd")) {
6913           /* Special kludge for GNU Chess 4 only */
6914           cps->sdKludge = TRUE;
6915           SendTimeControl(cps, movesPerSession, timeControl,
6916                           timeIncrement, appData.searchDepth,
6917                           searchTime);
6918           return;
6919         }
6920         if (!StrStr(message, "llegal")) {
6921             return;
6922         }
6923         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6924             gameMode == IcsIdle) return;
6925         if (forwardMostMove <= backwardMostMove) return;
6926         if (pausing) PauseEvent();
6927       if(appData.forceIllegal) {
6928             // [HGM] illegal: machine refused move; force position after move into it
6929           SendToProgram("force\n", cps);
6930           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6931                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6932                 // when black is to move, while there might be nothing on a2 or black
6933                 // might already have the move. So send the board as if white has the move.
6934                 // But first we must change the stm of the engine, as it refused the last move
6935                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6936                 if(WhiteOnMove(forwardMostMove)) {
6937                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6938                     SendBoard(cps, forwardMostMove); // kludgeless board
6939                 } else {
6940                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6941                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6942                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6943                 }
6944           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6945             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6946                  gameMode == TwoMachinesPlay)
6947               SendToProgram("go\n", cps);
6948             return;
6949       } else
6950         if (gameMode == PlayFromGameFile) {
6951             /* Stop reading this game file */
6952             gameMode = EditGame;
6953             ModeHighlight();
6954         }
6955         currentMove = --forwardMostMove;
6956         DisplayMove(currentMove-1); /* before DisplayMoveError */
6957         SwitchClocks();
6958         DisplayBothClocks();
6959         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6960                 parseList[currentMove], cps->which);
6961         DisplayMoveError(buf1);
6962         DrawPosition(FALSE, boards[currentMove]);
6963
6964         /* [HGM] illegal-move claim should forfeit game when Xboard */
6965         /* only passes fully legal moves                            */
6966         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6967             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6968                                 "False illegal-move claim", GE_XBOARD );
6969         }
6970         return;
6971     }
6972     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6973         /* Program has a broken "time" command that
6974            outputs a string not ending in newline.
6975            Don't use it. */
6976         cps->sendTime = 0;
6977     }
6978     
6979     /*
6980      * If chess program startup fails, exit with an error message.
6981      * Attempts to recover here are futile.
6982      */
6983     if ((StrStr(message, "unknown host") != NULL)
6984         || (StrStr(message, "No remote directory") != NULL)
6985         || (StrStr(message, "not found") != NULL)
6986         || (StrStr(message, "No such file") != NULL)
6987         || (StrStr(message, "can't alloc") != NULL)
6988         || (StrStr(message, "Permission denied") != NULL)) {
6989
6990         cps->maybeThinking = FALSE;
6991         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6992                 cps->which, cps->program, cps->host, message);
6993         RemoveInputSource(cps->isr);
6994         DisplayFatalError(buf1, 0, 1);
6995         return;
6996     }
6997     
6998     /* 
6999      * Look for hint output
7000      */
7001     if (sscanf(message, "Hint: %s", buf1) == 1) {
7002         if (cps == &first && hintRequested) {
7003             hintRequested = FALSE;
7004             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7005                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7006                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7007                                     PosFlags(forwardMostMove),
7008                                     fromY, fromX, toY, toX, promoChar, buf1);
7009                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7010                 DisplayInformation(buf2);
7011             } else {
7012                 /* Hint move could not be parsed!? */
7013               snprintf(buf2, sizeof(buf2),
7014                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7015                         buf1, cps->which);
7016                 DisplayError(buf2, 0);
7017             }
7018         } else {
7019             strcpy(lastHint, buf1);
7020         }
7021         return;
7022     }
7023
7024     /*
7025      * Ignore other messages if game is not in progress
7026      */
7027     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7028         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7029
7030     /*
7031      * look for win, lose, draw, or draw offer
7032      */
7033     if (strncmp(message, "1-0", 3) == 0) {
7034         char *p, *q, *r = "";
7035         p = strchr(message, '{');
7036         if (p) {
7037             q = strchr(p, '}');
7038             if (q) {
7039                 *q = NULLCHAR;
7040                 r = p + 1;
7041             }
7042         }
7043         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7044         return;
7045     } else if (strncmp(message, "0-1", 3) == 0) {
7046         char *p, *q, *r = "";
7047         p = strchr(message, '{');
7048         if (p) {
7049             q = strchr(p, '}');
7050             if (q) {
7051                 *q = NULLCHAR;
7052                 r = p + 1;
7053             }
7054         }
7055         /* Kludge for Arasan 4.1 bug */
7056         if (strcmp(r, "Black resigns") == 0) {
7057             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7058             return;
7059         }
7060         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7061         return;
7062     } else if (strncmp(message, "1/2", 3) == 0) {
7063         char *p, *q, *r = "";
7064         p = strchr(message, '{');
7065         if (p) {
7066             q = strchr(p, '}');
7067             if (q) {
7068                 *q = NULLCHAR;
7069                 r = p + 1;
7070             }
7071         }
7072             
7073         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7074         return;
7075
7076     } else if (strncmp(message, "White resign", 12) == 0) {
7077         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7078         return;
7079     } else if (strncmp(message, "Black resign", 12) == 0) {
7080         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7081         return;
7082     } else if (strncmp(message, "White matches", 13) == 0 ||
7083                strncmp(message, "Black matches", 13) == 0   ) {
7084         /* [HGM] ignore GNUShogi noises */
7085         return;
7086     } else if (strncmp(message, "White", 5) == 0 &&
7087                message[5] != '(' &&
7088                StrStr(message, "Black") == NULL) {
7089         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7090         return;
7091     } else if (strncmp(message, "Black", 5) == 0 &&
7092                message[5] != '(') {
7093         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7094         return;
7095     } else if (strcmp(message, "resign") == 0 ||
7096                strcmp(message, "computer resigns") == 0) {
7097         switch (gameMode) {
7098           case MachinePlaysBlack:
7099           case IcsPlayingBlack:
7100             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7101             break;
7102           case MachinePlaysWhite:
7103           case IcsPlayingWhite:
7104             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7105             break;
7106           case TwoMachinesPlay:
7107             if (cps->twoMachinesColor[0] == 'w')
7108               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7109             else
7110               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7111             break;
7112           default:
7113             /* can't happen */
7114             break;
7115         }
7116         return;
7117     } else if (strncmp(message, "opponent mates", 14) == 0) {
7118         switch (gameMode) {
7119           case MachinePlaysBlack:
7120           case IcsPlayingBlack:
7121             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7122             break;
7123           case MachinePlaysWhite:
7124           case IcsPlayingWhite:
7125             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7126             break;
7127           case TwoMachinesPlay:
7128             if (cps->twoMachinesColor[0] == 'w')
7129               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7130             else
7131               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7132             break;
7133           default:
7134             /* can't happen */
7135             break;
7136         }
7137         return;
7138     } else if (strncmp(message, "computer mates", 14) == 0) {
7139         switch (gameMode) {
7140           case MachinePlaysBlack:
7141           case IcsPlayingBlack:
7142             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7143             break;
7144           case MachinePlaysWhite:
7145           case IcsPlayingWhite:
7146             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7147             break;
7148           case TwoMachinesPlay:
7149             if (cps->twoMachinesColor[0] == 'w')
7150               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7151             else
7152               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7153             break;
7154           default:
7155             /* can't happen */
7156             break;
7157         }
7158         return;
7159     } else if (strncmp(message, "checkmate", 9) == 0) {
7160         if (WhiteOnMove(forwardMostMove)) {
7161             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7162         } else {
7163             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7164         }
7165         return;
7166     } else if (strstr(message, "Draw") != NULL ||
7167                strstr(message, "game is a draw") != NULL) {
7168         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7169         return;
7170     } else if (strstr(message, "offer") != NULL &&
7171                strstr(message, "draw") != NULL) {
7172 #if ZIPPY
7173         if (appData.zippyPlay && first.initDone) {
7174             /* Relay offer to ICS */
7175             SendToICS(ics_prefix);
7176             SendToICS("draw\n");
7177         }
7178 #endif
7179         cps->offeredDraw = 2; /* valid until this engine moves twice */
7180         if (gameMode == TwoMachinesPlay) {
7181             if (cps->other->offeredDraw) {
7182                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7183             /* [HGM] in two-machine mode we delay relaying draw offer      */
7184             /* until after we also have move, to see if it is really claim */
7185             }
7186         } else if (gameMode == MachinePlaysWhite ||
7187                    gameMode == MachinePlaysBlack) {
7188           if (userOfferedDraw) {
7189             DisplayInformation(_("Machine accepts your draw offer"));
7190             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7191           } else {
7192             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7193           }
7194         }
7195     }
7196
7197     
7198     /*
7199      * Look for thinking output
7200      */
7201     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7202           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7203                                 ) {
7204         int plylev, mvleft, mvtot, curscore, time;
7205         char mvname[MOVE_LEN];
7206         u64 nodes; // [DM]
7207         char plyext;
7208         int ignore = FALSE;
7209         int prefixHint = FALSE;
7210         mvname[0] = NULLCHAR;
7211
7212         switch (gameMode) {
7213           case MachinePlaysBlack:
7214           case IcsPlayingBlack:
7215             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7216             break;
7217           case MachinePlaysWhite:
7218           case IcsPlayingWhite:
7219             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7220             break;
7221           case AnalyzeMode:
7222           case AnalyzeFile:
7223             break;
7224           case IcsObserving: /* [DM] icsEngineAnalyze */
7225             if (!appData.icsEngineAnalyze) ignore = TRUE;
7226             break;
7227           case TwoMachinesPlay:
7228             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7229                 ignore = TRUE;
7230             }
7231             break;
7232           default:
7233             ignore = TRUE;
7234             break;
7235         }
7236
7237         if (!ignore) {
7238             buf1[0] = NULLCHAR;
7239             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7240                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7241
7242                 if (plyext != ' ' && plyext != '\t') {
7243                     time *= 100;
7244                 }
7245
7246                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7247                 if( cps->scoreIsAbsolute && 
7248                     ( gameMode == MachinePlaysBlack ||
7249                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7250                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7251                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7252                      !WhiteOnMove(currentMove)
7253                     ) )
7254                 {
7255                     curscore = -curscore;
7256                 }
7257
7258
7259                 programStats.depth = plylev;
7260                 programStats.nodes = nodes;
7261                 programStats.time = time;
7262                 programStats.score = curscore;
7263                 programStats.got_only_move = 0;
7264
7265                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7266                         int ticklen;
7267
7268                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7269                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7270                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7271                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7272                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7273                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7274                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7275                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7276                 }
7277
7278                 /* Buffer overflow protection */
7279                 if (buf1[0] != NULLCHAR) {
7280                     if (strlen(buf1) >= sizeof(programStats.movelist)
7281                         && appData.debugMode) {
7282                         fprintf(debugFP,
7283                                 "PV is too long; using the first %u bytes.\n",
7284                                 (unsigned) sizeof(programStats.movelist) - 1);
7285                     }
7286
7287                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7288                 } else {
7289                     sprintf(programStats.movelist, " no PV\n");
7290                 }
7291
7292                 if (programStats.seen_stat) {
7293                     programStats.ok_to_send = 1;
7294                 }
7295
7296                 if (strchr(programStats.movelist, '(') != NULL) {
7297                     programStats.line_is_book = 1;
7298                     programStats.nr_moves = 0;
7299                     programStats.moves_left = 0;
7300                 } else {
7301                     programStats.line_is_book = 0;
7302                 }
7303
7304                 SendProgramStatsToFrontend( cps, &programStats );
7305
7306                 /* 
7307                     [AS] Protect the thinkOutput buffer from overflow... this
7308                     is only useful if buf1 hasn't overflowed first!
7309                 */
7310                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7311                         plylev, 
7312                         (gameMode == TwoMachinesPlay ?
7313                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7314                         ((double) curscore) / 100.0,
7315                         prefixHint ? lastHint : "",
7316                         prefixHint ? " " : "" );
7317
7318                 if( buf1[0] != NULLCHAR ) {
7319                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7320
7321                     if( strlen(buf1) > max_len ) {
7322                         if( appData.debugMode) {
7323                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7324                         }
7325                         buf1[max_len+1] = '\0';
7326                     }
7327
7328                     strcat( thinkOutput, buf1 );
7329                 }
7330
7331                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7332                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7333                     DisplayMove(currentMove - 1);
7334                 }
7335                 return;
7336
7337             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7338                 /* crafty (9.25+) says "(only move) <move>"
7339                  * if there is only 1 legal move
7340                  */
7341                 sscanf(p, "(only move) %s", buf1);
7342                 sprintf(thinkOutput, "%s (only move)", buf1);
7343                 sprintf(programStats.movelist, "%s (only move)", buf1);
7344                 programStats.depth = 1;
7345                 programStats.nr_moves = 1;
7346                 programStats.moves_left = 1;
7347                 programStats.nodes = 1;
7348                 programStats.time = 1;
7349                 programStats.got_only_move = 1;
7350
7351                 /* Not really, but we also use this member to
7352                    mean "line isn't going to change" (Crafty
7353                    isn't searching, so stats won't change) */
7354                 programStats.line_is_book = 1;
7355
7356                 SendProgramStatsToFrontend( cps, &programStats );
7357                 
7358                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7359                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7360                     DisplayMove(currentMove - 1);
7361                 }
7362                 return;
7363             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7364                               &time, &nodes, &plylev, &mvleft,
7365                               &mvtot, mvname) >= 5) {
7366                 /* The stat01: line is from Crafty (9.29+) in response
7367                    to the "." command */
7368                 programStats.seen_stat = 1;
7369                 cps->maybeThinking = TRUE;
7370
7371                 if (programStats.got_only_move || !appData.periodicUpdates)
7372                   return;
7373
7374                 programStats.depth = plylev;
7375                 programStats.time = time;
7376                 programStats.nodes = nodes;
7377                 programStats.moves_left = mvleft;
7378                 programStats.nr_moves = mvtot;
7379                 strcpy(programStats.move_name, mvname);
7380                 programStats.ok_to_send = 1;
7381                 programStats.movelist[0] = '\0';
7382
7383                 SendProgramStatsToFrontend( cps, &programStats );
7384
7385                 return;
7386
7387             } else if (strncmp(message,"++",2) == 0) {
7388                 /* Crafty 9.29+ outputs this */
7389                 programStats.got_fail = 2;
7390                 return;
7391
7392             } else if (strncmp(message,"--",2) == 0) {
7393                 /* Crafty 9.29+ outputs this */
7394                 programStats.got_fail = 1;
7395                 return;
7396
7397             } else if (thinkOutput[0] != NULLCHAR &&
7398                        strncmp(message, "    ", 4) == 0) {
7399                 unsigned message_len;
7400
7401                 p = message;
7402                 while (*p && *p == ' ') p++;
7403
7404                 message_len = strlen( p );
7405
7406                 /* [AS] Avoid buffer overflow */
7407                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7408                     strcat(thinkOutput, " ");
7409                     strcat(thinkOutput, p);
7410                 }
7411
7412                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7413                     strcat(programStats.movelist, " ");
7414                     strcat(programStats.movelist, p);
7415                 }
7416
7417                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7418                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7419                     DisplayMove(currentMove - 1);
7420                 }
7421                 return;
7422             }
7423         }
7424         else {
7425             buf1[0] = NULLCHAR;
7426
7427             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7428                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7429             {
7430                 ChessProgramStats cpstats;
7431
7432                 if (plyext != ' ' && plyext != '\t') {
7433                     time *= 100;
7434                 }
7435
7436                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7437                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7438                     curscore = -curscore;
7439                 }
7440
7441                 cpstats.depth = plylev;
7442                 cpstats.nodes = nodes;
7443                 cpstats.time = time;
7444                 cpstats.score = curscore;
7445                 cpstats.got_only_move = 0;
7446                 cpstats.movelist[0] = '\0';
7447
7448                 if (buf1[0] != NULLCHAR) {
7449                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7450                 }
7451
7452                 cpstats.ok_to_send = 0;
7453                 cpstats.line_is_book = 0;
7454                 cpstats.nr_moves = 0;
7455                 cpstats.moves_left = 0;
7456
7457                 SendProgramStatsToFrontend( cps, &cpstats );
7458             }
7459         }
7460     }
7461 }
7462
7463
7464 /* Parse a game score from the character string "game", and
7465    record it as the history of the current game.  The game
7466    score is NOT assumed to start from the standard position. 
7467    The display is not updated in any way.
7468    */
7469 void
7470 ParseGameHistory(game)
7471      char *game;
7472 {
7473     ChessMove moveType;
7474     int fromX, fromY, toX, toY, boardIndex;
7475     char promoChar;
7476     char *p, *q;
7477     char buf[MSG_SIZ];
7478
7479     if (appData.debugMode)
7480       fprintf(debugFP, "Parsing game history: %s\n", game);
7481
7482     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7483     gameInfo.site = StrSave(appData.icsHost);
7484     gameInfo.date = PGNDate();
7485     gameInfo.round = StrSave("-");
7486
7487     /* Parse out names of players */
7488     while (*game == ' ') game++;
7489     p = buf;
7490     while (*game != ' ') *p++ = *game++;
7491     *p = NULLCHAR;
7492     gameInfo.white = StrSave(buf);
7493     while (*game == ' ') game++;
7494     p = buf;
7495     while (*game != ' ' && *game != '\n') *p++ = *game++;
7496     *p = NULLCHAR;
7497     gameInfo.black = StrSave(buf);
7498
7499     /* Parse moves */
7500     boardIndex = blackPlaysFirst ? 1 : 0;
7501     yynewstr(game);
7502     for (;;) {
7503         yyboardindex = boardIndex;
7504         moveType = (ChessMove) yylex();
7505         switch (moveType) {
7506           case IllegalMove:             /* maybe suicide chess, etc. */
7507   if (appData.debugMode) {
7508     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7509     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7510     setbuf(debugFP, NULL);
7511   }
7512           case WhitePromotionChancellor:
7513           case BlackPromotionChancellor:
7514           case WhitePromotionArchbishop:
7515           case BlackPromotionArchbishop:
7516           case WhitePromotionQueen:
7517           case BlackPromotionQueen:
7518           case WhitePromotionRook:
7519           case BlackPromotionRook:
7520           case WhitePromotionBishop:
7521           case BlackPromotionBishop:
7522           case WhitePromotionKnight:
7523           case BlackPromotionKnight:
7524           case WhitePromotionKing:
7525           case BlackPromotionKing:
7526           case NormalMove:
7527           case WhiteCapturesEnPassant:
7528           case BlackCapturesEnPassant:
7529           case WhiteKingSideCastle:
7530           case WhiteQueenSideCastle:
7531           case BlackKingSideCastle:
7532           case BlackQueenSideCastle:
7533           case WhiteKingSideCastleWild:
7534           case WhiteQueenSideCastleWild:
7535           case BlackKingSideCastleWild:
7536           case BlackQueenSideCastleWild:
7537           /* PUSH Fabien */
7538           case WhiteHSideCastleFR:
7539           case WhiteASideCastleFR:
7540           case BlackHSideCastleFR:
7541           case BlackASideCastleFR:
7542           /* POP Fabien */
7543             fromX = currentMoveString[0] - AAA;
7544             fromY = currentMoveString[1] - ONE;
7545             toX = currentMoveString[2] - AAA;
7546             toY = currentMoveString[3] - ONE;
7547             promoChar = currentMoveString[4];
7548             break;
7549           case WhiteDrop:
7550           case BlackDrop:
7551             fromX = moveType == WhiteDrop ?
7552               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7553             (int) CharToPiece(ToLower(currentMoveString[0]));
7554             fromY = DROP_RANK;
7555             toX = currentMoveString[2] - AAA;
7556             toY = currentMoveString[3] - ONE;
7557             promoChar = NULLCHAR;
7558             break;
7559           case AmbiguousMove:
7560             /* bug? */
7561             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7562   if (appData.debugMode) {
7563     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7564     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7565     setbuf(debugFP, NULL);
7566   }
7567             DisplayError(buf, 0);
7568             return;
7569           case ImpossibleMove:
7570             /* bug? */
7571             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7572   if (appData.debugMode) {
7573     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7574     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7575     setbuf(debugFP, NULL);
7576   }
7577             DisplayError(buf, 0);
7578             return;
7579           case (ChessMove) 0:   /* end of file */
7580             if (boardIndex < backwardMostMove) {
7581                 /* Oops, gap.  How did that happen? */
7582                 DisplayError(_("Gap in move list"), 0);
7583                 return;
7584             }
7585             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7586             if (boardIndex > forwardMostMove) {
7587                 forwardMostMove = boardIndex;
7588             }
7589             return;
7590           case ElapsedTime:
7591             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7592                 strcat(parseList[boardIndex-1], " ");
7593                 strcat(parseList[boardIndex-1], yy_text);
7594             }
7595             continue;
7596           case Comment:
7597           case PGNTag:
7598           case NAG:
7599           default:
7600             /* ignore */
7601             continue;
7602           case WhiteWins:
7603           case BlackWins:
7604           case GameIsDrawn:
7605           case GameUnfinished:
7606             if (gameMode == IcsExamining) {
7607                 if (boardIndex < backwardMostMove) {
7608                     /* Oops, gap.  How did that happen? */
7609                     return;
7610                 }
7611                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7612                 return;
7613             }
7614             gameInfo.result = moveType;
7615             p = strchr(yy_text, '{');
7616             if (p == NULL) p = strchr(yy_text, '(');
7617             if (p == NULL) {
7618                 p = yy_text;
7619                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7620             } else {
7621                 q = strchr(p, *p == '{' ? '}' : ')');
7622                 if (q != NULL) *q = NULLCHAR;
7623                 p++;
7624             }
7625             gameInfo.resultDetails = StrSave(p);
7626             continue;
7627         }
7628         if (boardIndex >= forwardMostMove &&
7629             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7630             backwardMostMove = blackPlaysFirst ? 1 : 0;
7631             return;
7632         }
7633         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7634                                  fromY, fromX, toY, toX, promoChar,
7635                                  parseList[boardIndex]);
7636         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7637         /* currentMoveString is set as a side-effect of yylex */
7638         strcpy(moveList[boardIndex], currentMoveString);
7639         strcat(moveList[boardIndex], "\n");
7640         boardIndex++;
7641         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7642         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7643           case MT_NONE:
7644           case MT_STALEMATE:
7645           default:
7646             break;
7647           case MT_CHECK:
7648             if(gameInfo.variant != VariantShogi)
7649                 strcat(parseList[boardIndex - 1], "+");
7650             break;
7651           case MT_CHECKMATE:
7652           case MT_STAINMATE:
7653             strcat(parseList[boardIndex - 1], "#");
7654             break;
7655         }
7656     }
7657 }
7658
7659
7660 /* Apply a move to the given board  */
7661 void
7662 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7663      int fromX, fromY, toX, toY;
7664      int promoChar;
7665      Board board;
7666 {
7667   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7668   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7669
7670     /* [HGM] compute & store e.p. status and castling rights for new position */
7671     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7672     { int i;
7673
7674       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7675       oldEP = (signed char)board[EP_STATUS];
7676       board[EP_STATUS] = EP_NONE;
7677
7678       if( board[toY][toX] != EmptySquare ) 
7679            board[EP_STATUS] = EP_CAPTURE;  
7680
7681       if( board[fromY][fromX] == WhitePawn ) {
7682            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7683                board[EP_STATUS] = EP_PAWN_MOVE;
7684            if( toY-fromY==2) {
7685                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7686                         gameInfo.variant != VariantBerolina || toX < fromX)
7687                       board[EP_STATUS] = toX | berolina;
7688                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7689                         gameInfo.variant != VariantBerolina || toX > fromX) 
7690                       board[EP_STATUS] = toX;
7691            }
7692       } else 
7693       if( board[fromY][fromX] == BlackPawn ) {
7694            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7695                board[EP_STATUS] = EP_PAWN_MOVE; 
7696            if( toY-fromY== -2) {
7697                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7698                         gameInfo.variant != VariantBerolina || toX < fromX)
7699                       board[EP_STATUS] = toX | berolina;
7700                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7701                         gameInfo.variant != VariantBerolina || toX > fromX) 
7702                       board[EP_STATUS] = toX;
7703            }
7704        }
7705
7706        for(i=0; i<nrCastlingRights; i++) {
7707            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7708               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7709              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7710        }
7711
7712     }
7713
7714   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7715   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7716        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7717          
7718   if (fromX == toX && fromY == toY) return;
7719
7720   if (fromY == DROP_RANK) {
7721         /* must be first */
7722         piece = board[toY][toX] = (ChessSquare) fromX;
7723   } else {
7724      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7725      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7726      if(gameInfo.variant == VariantKnightmate)
7727          king += (int) WhiteUnicorn - (int) WhiteKing;
7728
7729     /* Code added by Tord: */
7730     /* FRC castling assumed when king captures friendly rook. */
7731     if (board[fromY][fromX] == WhiteKing &&
7732              board[toY][toX] == WhiteRook) {
7733       board[fromY][fromX] = EmptySquare;
7734       board[toY][toX] = EmptySquare;
7735       if(toX > fromX) {
7736         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7737       } else {
7738         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7739       }
7740     } else if (board[fromY][fromX] == BlackKing &&
7741                board[toY][toX] == BlackRook) {
7742       board[fromY][fromX] = EmptySquare;
7743       board[toY][toX] = EmptySquare;
7744       if(toX > fromX) {
7745         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7746       } else {
7747         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7748       }
7749     /* End of code added by Tord */
7750
7751     } else if (board[fromY][fromX] == king
7752         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7753         && toY == fromY && toX > fromX+1) {
7754         board[fromY][fromX] = EmptySquare;
7755         board[toY][toX] = king;
7756         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7757         board[fromY][BOARD_RGHT-1] = EmptySquare;
7758     } else if (board[fromY][fromX] == king
7759         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7760                && toY == fromY && toX < fromX-1) {
7761         board[fromY][fromX] = EmptySquare;
7762         board[toY][toX] = king;
7763         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7764         board[fromY][BOARD_LEFT] = EmptySquare;
7765     } else if (board[fromY][fromX] == WhitePawn
7766                && toY >= BOARD_HEIGHT-promoRank
7767                && gameInfo.variant != VariantXiangqi
7768                ) {
7769         /* white pawn promotion */
7770         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7771         if (board[toY][toX] == EmptySquare) {
7772             board[toY][toX] = WhiteQueen;
7773         }
7774         if(gameInfo.variant==VariantBughouse ||
7775            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7776             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7777         board[fromY][fromX] = EmptySquare;
7778     } else if ((fromY == BOARD_HEIGHT-4)
7779                && (toX != fromX)
7780                && gameInfo.variant != VariantXiangqi
7781                && gameInfo.variant != VariantBerolina
7782                && (board[fromY][fromX] == WhitePawn)
7783                && (board[toY][toX] == EmptySquare)) {
7784         board[fromY][fromX] = EmptySquare;
7785         board[toY][toX] = WhitePawn;
7786         captured = board[toY - 1][toX];
7787         board[toY - 1][toX] = EmptySquare;
7788     } else if ((fromY == BOARD_HEIGHT-4)
7789                && (toX == fromX)
7790                && gameInfo.variant == VariantBerolina
7791                && (board[fromY][fromX] == WhitePawn)
7792                && (board[toY][toX] == EmptySquare)) {
7793         board[fromY][fromX] = EmptySquare;
7794         board[toY][toX] = WhitePawn;
7795         if(oldEP & EP_BEROLIN_A) {
7796                 captured = board[fromY][fromX-1];
7797                 board[fromY][fromX-1] = EmptySquare;
7798         }else{  captured = board[fromY][fromX+1];
7799                 board[fromY][fromX+1] = EmptySquare;
7800         }
7801     } else if (board[fromY][fromX] == king
7802         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7803                && toY == fromY && toX > fromX+1) {
7804         board[fromY][fromX] = EmptySquare;
7805         board[toY][toX] = king;
7806         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7807         board[fromY][BOARD_RGHT-1] = EmptySquare;
7808     } else if (board[fromY][fromX] == king
7809         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7810                && toY == fromY && toX < fromX-1) {
7811         board[fromY][fromX] = EmptySquare;
7812         board[toY][toX] = king;
7813         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7814         board[fromY][BOARD_LEFT] = EmptySquare;
7815     } else if (fromY == 7 && fromX == 3
7816                && board[fromY][fromX] == BlackKing
7817                && toY == 7 && toX == 5) {
7818         board[fromY][fromX] = EmptySquare;
7819         board[toY][toX] = BlackKing;
7820         board[fromY][7] = EmptySquare;
7821         board[toY][4] = BlackRook;
7822     } else if (fromY == 7 && fromX == 3
7823                && board[fromY][fromX] == BlackKing
7824                && toY == 7 && toX == 1) {
7825         board[fromY][fromX] = EmptySquare;
7826         board[toY][toX] = BlackKing;
7827         board[fromY][0] = EmptySquare;
7828         board[toY][2] = BlackRook;
7829     } else if (board[fromY][fromX] == BlackPawn
7830                && toY < promoRank
7831                && gameInfo.variant != VariantXiangqi
7832                ) {
7833         /* black pawn promotion */
7834         board[toY][toX] = CharToPiece(ToLower(promoChar));
7835         if (board[toY][toX] == EmptySquare) {
7836             board[toY][toX] = BlackQueen;
7837         }
7838         if(gameInfo.variant==VariantBughouse ||
7839            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7840             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7841         board[fromY][fromX] = EmptySquare;
7842     } else if ((fromY == 3)
7843                && (toX != fromX)
7844                && gameInfo.variant != VariantXiangqi
7845                && gameInfo.variant != VariantBerolina
7846                && (board[fromY][fromX] == BlackPawn)
7847                && (board[toY][toX] == EmptySquare)) {
7848         board[fromY][fromX] = EmptySquare;
7849         board[toY][toX] = BlackPawn;
7850         captured = board[toY + 1][toX];
7851         board[toY + 1][toX] = EmptySquare;
7852     } else if ((fromY == 3)
7853                && (toX == fromX)
7854                && gameInfo.variant == VariantBerolina
7855                && (board[fromY][fromX] == BlackPawn)
7856                && (board[toY][toX] == EmptySquare)) {
7857         board[fromY][fromX] = EmptySquare;
7858         board[toY][toX] = BlackPawn;
7859         if(oldEP & EP_BEROLIN_A) {
7860                 captured = board[fromY][fromX-1];
7861                 board[fromY][fromX-1] = EmptySquare;
7862         }else{  captured = board[fromY][fromX+1];
7863                 board[fromY][fromX+1] = EmptySquare;
7864         }
7865     } else {
7866         board[toY][toX] = board[fromY][fromX];
7867         board[fromY][fromX] = EmptySquare;
7868     }
7869
7870     /* [HGM] now we promote for Shogi, if needed */
7871     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7872         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7873   }
7874
7875     if (gameInfo.holdingsWidth != 0) {
7876
7877       /* !!A lot more code needs to be written to support holdings  */
7878       /* [HGM] OK, so I have written it. Holdings are stored in the */
7879       /* penultimate board files, so they are automaticlly stored   */
7880       /* in the game history.                                       */
7881       if (fromY == DROP_RANK) {
7882         /* Delete from holdings, by decreasing count */
7883         /* and erasing image if necessary            */
7884         p = (int) fromX;
7885         if(p < (int) BlackPawn) { /* white drop */
7886              p -= (int)WhitePawn;
7887                  p = PieceToNumber((ChessSquare)p);
7888              if(p >= gameInfo.holdingsSize) p = 0;
7889              if(--board[p][BOARD_WIDTH-2] <= 0)
7890                   board[p][BOARD_WIDTH-1] = EmptySquare;
7891              if((int)board[p][BOARD_WIDTH-2] < 0)
7892                         board[p][BOARD_WIDTH-2] = 0;
7893         } else {                  /* black drop */
7894              p -= (int)BlackPawn;
7895                  p = PieceToNumber((ChessSquare)p);
7896              if(p >= gameInfo.holdingsSize) p = 0;
7897              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7898                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7899              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7900                         board[BOARD_HEIGHT-1-p][1] = 0;
7901         }
7902       }
7903       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7904           && gameInfo.variant != VariantBughouse        ) {
7905         /* [HGM] holdings: Add to holdings, if holdings exist */
7906         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7907                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7908                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7909         }
7910         p = (int) captured;
7911         if (p >= (int) BlackPawn) {
7912           p -= (int)BlackPawn;
7913           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7914                   /* in Shogi restore piece to its original  first */
7915                   captured = (ChessSquare) (DEMOTED captured);
7916                   p = DEMOTED p;
7917           }
7918           p = PieceToNumber((ChessSquare)p);
7919           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7920           board[p][BOARD_WIDTH-2]++;
7921           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7922         } else {
7923           p -= (int)WhitePawn;
7924           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7925                   captured = (ChessSquare) (DEMOTED captured);
7926                   p = DEMOTED p;
7927           }
7928           p = PieceToNumber((ChessSquare)p);
7929           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7930           board[BOARD_HEIGHT-1-p][1]++;
7931           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7932         }
7933       }
7934     } else if (gameInfo.variant == VariantAtomic) {
7935       if (captured != EmptySquare) {
7936         int y, x;
7937         for (y = toY-1; y <= toY+1; y++) {
7938           for (x = toX-1; x <= toX+1; x++) {
7939             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7940                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7941               board[y][x] = EmptySquare;
7942             }
7943           }
7944         }
7945         board[toY][toX] = EmptySquare;
7946       }
7947     }
7948     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7949         /* [HGM] Shogi promotions */
7950         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7951     }
7952
7953     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7954                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7955         // [HGM] superchess: take promotion piece out of holdings
7956         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7957         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7958             if(!--board[k][BOARD_WIDTH-2])
7959                 board[k][BOARD_WIDTH-1] = EmptySquare;
7960         } else {
7961             if(!--board[BOARD_HEIGHT-1-k][1])
7962                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7963         }
7964     }
7965
7966 }
7967
7968 /* Updates forwardMostMove */
7969 void
7970 MakeMove(fromX, fromY, toX, toY, promoChar)
7971      int fromX, fromY, toX, toY;
7972      int promoChar;
7973 {
7974 //    forwardMostMove++; // [HGM] bare: moved downstream
7975
7976     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7977         int timeLeft; static int lastLoadFlag=0; int king, piece;
7978         piece = boards[forwardMostMove][fromY][fromX];
7979         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7980         if(gameInfo.variant == VariantKnightmate)
7981             king += (int) WhiteUnicorn - (int) WhiteKing;
7982         if(forwardMostMove == 0) {
7983             if(blackPlaysFirst) 
7984                 fprintf(serverMoves, "%s;", second.tidy);
7985             fprintf(serverMoves, "%s;", first.tidy);
7986             if(!blackPlaysFirst) 
7987                 fprintf(serverMoves, "%s;", second.tidy);
7988         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7989         lastLoadFlag = loadFlag;
7990         // print base move
7991         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7992         // print castling suffix
7993         if( toY == fromY && piece == king ) {
7994             if(toX-fromX > 1)
7995                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7996             if(fromX-toX >1)
7997                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7998         }
7999         // e.p. suffix
8000         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8001              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8002              boards[forwardMostMove][toY][toX] == EmptySquare
8003              && fromX != toX )
8004                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8005         // promotion suffix
8006         if(promoChar != NULLCHAR)
8007                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8008         if(!loadFlag) {
8009             fprintf(serverMoves, "/%d/%d",
8010                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8011             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8012             else                      timeLeft = blackTimeRemaining/1000;
8013             fprintf(serverMoves, "/%d", timeLeft);
8014         }
8015         fflush(serverMoves);
8016     }
8017
8018     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8019       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8020                         0, 1);
8021       return;
8022     }
8023     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8024     if (commentList[forwardMostMove+1] != NULL) {
8025         free(commentList[forwardMostMove+1]);
8026         commentList[forwardMostMove+1] = NULL;
8027     }
8028     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8029     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8030     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8031     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8032     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8033     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8034     gameInfo.result = GameUnfinished;
8035     if (gameInfo.resultDetails != NULL) {
8036         free(gameInfo.resultDetails);
8037         gameInfo.resultDetails = NULL;
8038     }
8039     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8040                               moveList[forwardMostMove - 1]);
8041     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8042                              PosFlags(forwardMostMove - 1),
8043                              fromY, fromX, toY, toX, promoChar,
8044                              parseList[forwardMostMove - 1]);
8045     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8046       case MT_NONE:
8047       case MT_STALEMATE:
8048       default:
8049         break;
8050       case MT_CHECK:
8051         if(gameInfo.variant != VariantShogi)
8052             strcat(parseList[forwardMostMove - 1], "+");
8053         break;
8054       case MT_CHECKMATE:
8055       case MT_STAINMATE:
8056         strcat(parseList[forwardMostMove - 1], "#");
8057         break;
8058     }
8059     if (appData.debugMode) {
8060         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8061     }
8062
8063 }
8064
8065 /* Updates currentMove if not pausing */
8066 void
8067 ShowMove(fromX, fromY, toX, toY)
8068 {
8069     int instant = (gameMode == PlayFromGameFile) ?
8070         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8071     if(appData.noGUI) return;
8072     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8073         if (!instant) {
8074             if (forwardMostMove == currentMove + 1) {
8075                 AnimateMove(boards[forwardMostMove - 1],
8076                             fromX, fromY, toX, toY);
8077             }
8078             if (appData.highlightLastMove) {
8079                 SetHighlights(fromX, fromY, toX, toY);
8080             }
8081         }
8082         currentMove = forwardMostMove;
8083     }
8084
8085     if (instant) return;
8086
8087     DisplayMove(currentMove - 1);
8088     DrawPosition(FALSE, boards[currentMove]);
8089     DisplayBothClocks();
8090     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8091 }
8092
8093 void SendEgtPath(ChessProgramState *cps)
8094 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8095         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8096
8097         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8098
8099         while(*p) {
8100             char c, *q = name+1, *r, *s;
8101
8102             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8103             while(*p && *p != ',') *q++ = *p++;
8104             *q++ = ':'; *q = 0;
8105             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8106                 strcmp(name, ",nalimov:") == 0 ) {
8107                 // take nalimov path from the menu-changeable option first, if it is defined
8108                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8109                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8110             } else
8111             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8112                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8113                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8114                 s = r = StrStr(s, ":") + 1; // beginning of path info
8115                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8116                 c = *r; *r = 0;             // temporarily null-terminate path info
8117                     *--q = 0;               // strip of trailig ':' from name
8118                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8119                 *r = c;
8120                 SendToProgram(buf,cps);     // send egtbpath command for this format
8121             }
8122             if(*p == ',') p++; // read away comma to position for next format name
8123         }
8124 }
8125
8126 void
8127 InitChessProgram(cps, setup)
8128      ChessProgramState *cps;
8129      int setup; /* [HGM] needed to setup FRC opening position */
8130 {
8131     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8132     if (appData.noChessProgram) return;
8133     hintRequested = FALSE;
8134     bookRequested = FALSE;
8135
8136     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8137     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8138     if(cps->memSize) { /* [HGM] memory */
8139         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8140         SendToProgram(buf, cps);
8141     }
8142     SendEgtPath(cps); /* [HGM] EGT */
8143     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8144         sprintf(buf, "cores %d\n", appData.smpCores);
8145         SendToProgram(buf, cps);
8146     }
8147
8148     SendToProgram(cps->initString, cps);
8149     if (gameInfo.variant != VariantNormal &&
8150         gameInfo.variant != VariantLoadable
8151         /* [HGM] also send variant if board size non-standard */
8152         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8153                                             ) {
8154       char *v = VariantName(gameInfo.variant);
8155       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8156         /* [HGM] in protocol 1 we have to assume all variants valid */
8157         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8158         DisplayFatalError(buf, 0, 1);
8159         return;
8160       }
8161
8162       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8163       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8164       if( gameInfo.variant == VariantXiangqi )
8165            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8166       if( gameInfo.variant == VariantShogi )
8167            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8168       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8169            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8170       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8171                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8172            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8173       if( gameInfo.variant == VariantCourier )
8174            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8175       if( gameInfo.variant == VariantSuper )
8176            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8177       if( gameInfo.variant == VariantGreat )
8178            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8179
8180       if(overruled) {
8181            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8182                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8183            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8184            if(StrStr(cps->variants, b) == NULL) { 
8185                // specific sized variant not known, check if general sizing allowed
8186                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8187                    if(StrStr(cps->variants, "boardsize") == NULL) {
8188                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8189                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8190                        DisplayFatalError(buf, 0, 1);
8191                        return;
8192                    }
8193                    /* [HGM] here we really should compare with the maximum supported board size */
8194                }
8195            }
8196       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8197       sprintf(buf, "variant %s\n", b);
8198       SendToProgram(buf, cps);
8199     }
8200     currentlyInitializedVariant = gameInfo.variant;
8201
8202     /* [HGM] send opening position in FRC to first engine */
8203     if(setup) {
8204           SendToProgram("force\n", cps);
8205           SendBoard(cps, 0);
8206           /* engine is now in force mode! Set flag to wake it up after first move. */
8207           setboardSpoiledMachineBlack = 1;
8208     }
8209
8210     if (cps->sendICS) {
8211       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8212       SendToProgram(buf, cps);
8213     }
8214     cps->maybeThinking = FALSE;
8215     cps->offeredDraw = 0;
8216     if (!appData.icsActive) {
8217         SendTimeControl(cps, movesPerSession, timeControl,
8218                         timeIncrement, appData.searchDepth,
8219                         searchTime);
8220     }
8221     if (appData.showThinking 
8222         // [HGM] thinking: four options require thinking output to be sent
8223         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8224                                 ) {
8225         SendToProgram("post\n", cps);
8226     }
8227     SendToProgram("hard\n", cps);
8228     if (!appData.ponderNextMove) {
8229         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8230            it without being sure what state we are in first.  "hard"
8231            is not a toggle, so that one is OK.
8232          */
8233         SendToProgram("easy\n", cps);
8234     }
8235     if (cps->usePing) {
8236       sprintf(buf, "ping %d\n", ++cps->lastPing);
8237       SendToProgram(buf, cps);
8238     }
8239     cps->initDone = TRUE;
8240 }   
8241
8242
8243 void
8244 StartChessProgram(cps)
8245      ChessProgramState *cps;
8246 {
8247     char buf[MSG_SIZ];
8248     int err;
8249
8250     if (appData.noChessProgram) return;
8251     cps->initDone = FALSE;
8252
8253     if (strcmp(cps->host, "localhost") == 0) {
8254         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8255     } else if (*appData.remoteShell == NULLCHAR) {
8256         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8257     } else {
8258         if (*appData.remoteUser == NULLCHAR) {
8259           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8260                     cps->program);
8261         } else {
8262           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8263                     cps->host, appData.remoteUser, cps->program);
8264         }
8265         err = StartChildProcess(buf, "", &cps->pr);
8266     }
8267     
8268     if (err != 0) {
8269         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8270         DisplayFatalError(buf, err, 1);
8271         cps->pr = NoProc;
8272         cps->isr = NULL;
8273         return;
8274     }
8275     
8276     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8277     if (cps->protocolVersion > 1) {
8278       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8279       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8280       cps->comboCnt = 0;  //                and values of combo boxes
8281       SendToProgram(buf, cps);
8282     } else {
8283       SendToProgram("xboard\n", cps);
8284     }
8285 }
8286
8287
8288 void
8289 TwoMachinesEventIfReady P((void))
8290 {
8291   if (first.lastPing != first.lastPong) {
8292     DisplayMessage("", _("Waiting for first chess program"));
8293     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8294     return;
8295   }
8296   if (second.lastPing != second.lastPong) {
8297     DisplayMessage("", _("Waiting for second chess program"));
8298     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8299     return;
8300   }
8301   ThawUI();
8302   TwoMachinesEvent();
8303 }
8304
8305 void
8306 NextMatchGame P((void))
8307 {
8308     int index; /* [HGM] autoinc: step load index during match */
8309     Reset(FALSE, TRUE);
8310     if (*appData.loadGameFile != NULLCHAR) {
8311         index = appData.loadGameIndex;
8312         if(index < 0) { // [HGM] autoinc
8313             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8314             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8315         } 
8316         LoadGameFromFile(appData.loadGameFile,
8317                          index,
8318                          appData.loadGameFile, FALSE);
8319     } else if (*appData.loadPositionFile != NULLCHAR) {
8320         index = appData.loadPositionIndex;
8321         if(index < 0) { // [HGM] autoinc
8322             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8323             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8324         } 
8325         LoadPositionFromFile(appData.loadPositionFile,
8326                              index,
8327                              appData.loadPositionFile);
8328     }
8329     TwoMachinesEventIfReady();
8330 }
8331
8332 void UserAdjudicationEvent( int result )
8333 {
8334     ChessMove gameResult = GameIsDrawn;
8335
8336     if( result > 0 ) {
8337         gameResult = WhiteWins;
8338     }
8339     else if( result < 0 ) {
8340         gameResult = BlackWins;
8341     }
8342
8343     if( gameMode == TwoMachinesPlay ) {
8344         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8345     }
8346 }
8347
8348
8349 // [HGM] save: calculate checksum of game to make games easily identifiable
8350 int StringCheckSum(char *s)
8351 {
8352         int i = 0;
8353         if(s==NULL) return 0;
8354         while(*s) i = i*259 + *s++;
8355         return i;
8356 }
8357
8358 int GameCheckSum()
8359 {
8360         int i, sum=0;
8361         for(i=backwardMostMove; i<forwardMostMove; i++) {
8362                 sum += pvInfoList[i].depth;
8363                 sum += StringCheckSum(parseList[i]);
8364                 sum += StringCheckSum(commentList[i]);
8365                 sum *= 261;
8366         }
8367         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8368         return sum + StringCheckSum(commentList[i]);
8369 } // end of save patch
8370
8371 void
8372 GameEnds(result, resultDetails, whosays)
8373      ChessMove result;
8374      char *resultDetails;
8375      int whosays;
8376 {
8377     GameMode nextGameMode;
8378     int isIcsGame;
8379     char buf[MSG_SIZ];
8380
8381     if(endingGame) return; /* [HGM] crash: forbid recursion */
8382     endingGame = 1;
8383
8384     if (appData.debugMode) {
8385       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8386               result, resultDetails ? resultDetails : "(null)", whosays);
8387     }
8388
8389     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8390         /* If we are playing on ICS, the server decides when the
8391            game is over, but the engine can offer to draw, claim 
8392            a draw, or resign. 
8393          */
8394 #if ZIPPY
8395         if (appData.zippyPlay && first.initDone) {
8396             if (result == GameIsDrawn) {
8397                 /* In case draw still needs to be claimed */
8398                 SendToICS(ics_prefix);
8399                 SendToICS("draw\n");
8400             } else if (StrCaseStr(resultDetails, "resign")) {
8401                 SendToICS(ics_prefix);
8402                 SendToICS("resign\n");
8403             }
8404         }
8405 #endif
8406         endingGame = 0; /* [HGM] crash */
8407         return;
8408     }
8409
8410     /* If we're loading the game from a file, stop */
8411     if (whosays == GE_FILE) {
8412       (void) StopLoadGameTimer();
8413       gameFileFP = NULL;
8414     }
8415
8416     /* Cancel draw offers */
8417     first.offeredDraw = second.offeredDraw = 0;
8418
8419     /* If this is an ICS game, only ICS can really say it's done;
8420        if not, anyone can. */
8421     isIcsGame = (gameMode == IcsPlayingWhite || 
8422                  gameMode == IcsPlayingBlack || 
8423                  gameMode == IcsObserving    || 
8424                  gameMode == IcsExamining);
8425
8426     if (!isIcsGame || whosays == GE_ICS) {
8427         /* OK -- not an ICS game, or ICS said it was done */
8428         StopClocks();
8429         if (!isIcsGame && !appData.noChessProgram) 
8430           SetUserThinkingEnables();
8431     
8432         /* [HGM] if a machine claims the game end we verify this claim */
8433         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8434             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8435                 char claimer;
8436                 ChessMove trueResult = (ChessMove) -1;
8437
8438                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8439                                             first.twoMachinesColor[0] :
8440                                             second.twoMachinesColor[0] ;
8441
8442                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8443                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8444                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8445                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8446                 } else
8447                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8448                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8449                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8450                 } else
8451                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8452                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8453                 }
8454
8455                 // now verify win claims, but not in drop games, as we don't understand those yet
8456                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8457                                                  || gameInfo.variant == VariantGreat) &&
8458                     (result == WhiteWins && claimer == 'w' ||
8459                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8460                       if (appData.debugMode) {
8461                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8462                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8463                       }
8464                       if(result != trueResult) {
8465                               sprintf(buf, "False win claim: '%s'", resultDetails);
8466                               result = claimer == 'w' ? BlackWins : WhiteWins;
8467                               resultDetails = buf;
8468                       }
8469                 } else
8470                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8471                     && (forwardMostMove <= backwardMostMove ||
8472                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8473                         (claimer=='b')==(forwardMostMove&1))
8474                                                                                   ) {
8475                       /* [HGM] verify: draws that were not flagged are false claims */
8476                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8477                       result = claimer == 'w' ? BlackWins : WhiteWins;
8478                       resultDetails = buf;
8479                 }
8480                 /* (Claiming a loss is accepted no questions asked!) */
8481             }
8482             /* [HGM] bare: don't allow bare King to win */
8483             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8484                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8485                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8486                && result != GameIsDrawn)
8487             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8488                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8489                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8490                         if(p >= 0 && p <= (int)WhiteKing) k++;
8491                 }
8492                 if (appData.debugMode) {
8493                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8494                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8495                 }
8496                 if(k <= 1) {
8497                         result = GameIsDrawn;
8498                         sprintf(buf, "%s but bare king", resultDetails);
8499                         resultDetails = buf;
8500                 }
8501             }
8502         }
8503
8504
8505         if(serverMoves != NULL && !loadFlag) { char c = '=';
8506             if(result==WhiteWins) c = '+';
8507             if(result==BlackWins) c = '-';
8508             if(resultDetails != NULL)
8509                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8510         }
8511         if (resultDetails != NULL) {
8512             gameInfo.result = result;
8513             gameInfo.resultDetails = StrSave(resultDetails);
8514
8515             /* display last move only if game was not loaded from file */
8516             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8517                 DisplayMove(currentMove - 1);
8518     
8519             if (forwardMostMove != 0) {
8520                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8521                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8522                                                                 ) {
8523                     if (*appData.saveGameFile != NULLCHAR) {
8524                         SaveGameToFile(appData.saveGameFile, TRUE);
8525                     } else if (appData.autoSaveGames) {
8526                         AutoSaveGame();
8527                     }
8528                     if (*appData.savePositionFile != NULLCHAR) {
8529                         SavePositionToFile(appData.savePositionFile);
8530                     }
8531                 }
8532             }
8533
8534             /* Tell program how game ended in case it is learning */
8535             /* [HGM] Moved this to after saving the PGN, just in case */
8536             /* engine died and we got here through time loss. In that */
8537             /* case we will get a fatal error writing the pipe, which */
8538             /* would otherwise lose us the PGN.                       */
8539             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8540             /* output during GameEnds should never be fatal anymore   */
8541             if (gameMode == MachinePlaysWhite ||
8542                 gameMode == MachinePlaysBlack ||
8543                 gameMode == TwoMachinesPlay ||
8544                 gameMode == IcsPlayingWhite ||
8545                 gameMode == IcsPlayingBlack ||
8546                 gameMode == BeginningOfGame) {
8547                 char buf[MSG_SIZ];
8548                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8549                         resultDetails);
8550                 if (first.pr != NoProc) {
8551                     SendToProgram(buf, &first);
8552                 }
8553                 if (second.pr != NoProc &&
8554                     gameMode == TwoMachinesPlay) {
8555                     SendToProgram(buf, &second);
8556                 }
8557             }
8558         }
8559
8560         if (appData.icsActive) {
8561             if (appData.quietPlay &&
8562                 (gameMode == IcsPlayingWhite ||
8563                  gameMode == IcsPlayingBlack)) {
8564                 SendToICS(ics_prefix);
8565                 SendToICS("set shout 1\n");
8566             }
8567             nextGameMode = IcsIdle;
8568             ics_user_moved = FALSE;
8569             /* clean up premove.  It's ugly when the game has ended and the
8570              * premove highlights are still on the board.
8571              */
8572             if (gotPremove) {
8573               gotPremove = FALSE;
8574               ClearPremoveHighlights();
8575               DrawPosition(FALSE, boards[currentMove]);
8576             }
8577             if (whosays == GE_ICS) {
8578                 switch (result) {
8579                 case WhiteWins:
8580                     if (gameMode == IcsPlayingWhite)
8581                         PlayIcsWinSound();
8582                     else if(gameMode == IcsPlayingBlack)
8583                         PlayIcsLossSound();
8584                     break;
8585                 case BlackWins:
8586                     if (gameMode == IcsPlayingBlack)
8587                         PlayIcsWinSound();
8588                     else if(gameMode == IcsPlayingWhite)
8589                         PlayIcsLossSound();
8590                     break;
8591                 case GameIsDrawn:
8592                     PlayIcsDrawSound();
8593                     break;
8594                 default:
8595                     PlayIcsUnfinishedSound();
8596                 }
8597             }
8598         } else if (gameMode == EditGame ||
8599                    gameMode == PlayFromGameFile || 
8600                    gameMode == AnalyzeMode || 
8601                    gameMode == AnalyzeFile) {
8602             nextGameMode = gameMode;
8603         } else {
8604             nextGameMode = EndOfGame;
8605         }
8606         pausing = FALSE;
8607         ModeHighlight();
8608     } else {
8609         nextGameMode = gameMode;
8610     }
8611
8612     if (appData.noChessProgram) {
8613         gameMode = nextGameMode;
8614         ModeHighlight();
8615         endingGame = 0; /* [HGM] crash */
8616         return;
8617     }
8618
8619     if (first.reuse) {
8620         /* Put first chess program into idle state */
8621         if (first.pr != NoProc &&
8622             (gameMode == MachinePlaysWhite ||
8623              gameMode == MachinePlaysBlack ||
8624              gameMode == TwoMachinesPlay ||
8625              gameMode == IcsPlayingWhite ||
8626              gameMode == IcsPlayingBlack ||
8627              gameMode == BeginningOfGame)) {
8628             SendToProgram("force\n", &first);
8629             if (first.usePing) {
8630               char buf[MSG_SIZ];
8631               sprintf(buf, "ping %d\n", ++first.lastPing);
8632               SendToProgram(buf, &first);
8633             }
8634         }
8635     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8636         /* Kill off first chess program */
8637         if (first.isr != NULL)
8638           RemoveInputSource(first.isr);
8639         first.isr = NULL;
8640     
8641         if (first.pr != NoProc) {
8642             ExitAnalyzeMode();
8643             DoSleep( appData.delayBeforeQuit );
8644             SendToProgram("quit\n", &first);
8645             DoSleep( appData.delayAfterQuit );
8646             DestroyChildProcess(first.pr, first.useSigterm);
8647         }
8648         first.pr = NoProc;
8649     }
8650     if (second.reuse) {
8651         /* Put second chess program into idle state */
8652         if (second.pr != NoProc &&
8653             gameMode == TwoMachinesPlay) {
8654             SendToProgram("force\n", &second);
8655             if (second.usePing) {
8656               char buf[MSG_SIZ];
8657               sprintf(buf, "ping %d\n", ++second.lastPing);
8658               SendToProgram(buf, &second);
8659             }
8660         }
8661     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8662         /* Kill off second chess program */
8663         if (second.isr != NULL)
8664           RemoveInputSource(second.isr);
8665         second.isr = NULL;
8666     
8667         if (second.pr != NoProc) {
8668             DoSleep( appData.delayBeforeQuit );
8669             SendToProgram("quit\n", &second);
8670             DoSleep( appData.delayAfterQuit );
8671             DestroyChildProcess(second.pr, second.useSigterm);
8672         }
8673         second.pr = NoProc;
8674     }
8675
8676     if (matchMode && gameMode == TwoMachinesPlay) {
8677         switch (result) {
8678         case WhiteWins:
8679           if (first.twoMachinesColor[0] == 'w') {
8680             first.matchWins++;
8681           } else {
8682             second.matchWins++;
8683           }
8684           break;
8685         case BlackWins:
8686           if (first.twoMachinesColor[0] == 'b') {
8687             first.matchWins++;
8688           } else {
8689             second.matchWins++;
8690           }
8691           break;
8692         default:
8693           break;
8694         }
8695         if (matchGame < appData.matchGames) {
8696             char *tmp;
8697             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8698                 tmp = first.twoMachinesColor;
8699                 first.twoMachinesColor = second.twoMachinesColor;
8700                 second.twoMachinesColor = tmp;
8701             }
8702             gameMode = nextGameMode;
8703             matchGame++;
8704             if(appData.matchPause>10000 || appData.matchPause<10)
8705                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8706             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8707             endingGame = 0; /* [HGM] crash */
8708             return;
8709         } else {
8710             char buf[MSG_SIZ];
8711             gameMode = nextGameMode;
8712             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8713                     first.tidy, second.tidy,
8714                     first.matchWins, second.matchWins,
8715                     appData.matchGames - (first.matchWins + second.matchWins));
8716             DisplayFatalError(buf, 0, 0);
8717         }
8718     }
8719     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8720         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8721       ExitAnalyzeMode();
8722     gameMode = nextGameMode;
8723     ModeHighlight();
8724     endingGame = 0;  /* [HGM] crash */
8725 }
8726
8727 /* Assumes program was just initialized (initString sent).
8728    Leaves program in force mode. */
8729 void
8730 FeedMovesToProgram(cps, upto) 
8731      ChessProgramState *cps;
8732      int upto;
8733 {
8734     int i;
8735     
8736     if (appData.debugMode)
8737       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8738               startedFromSetupPosition ? "position and " : "",
8739               backwardMostMove, upto, cps->which);
8740     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8741         // [HGM] variantswitch: make engine aware of new variant
8742         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8743                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8744         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8745         SendToProgram(buf, cps);
8746         currentlyInitializedVariant = gameInfo.variant;
8747     }
8748     SendToProgram("force\n", cps);
8749     if (startedFromSetupPosition) {
8750         SendBoard(cps, backwardMostMove);
8751     if (appData.debugMode) {
8752         fprintf(debugFP, "feedMoves\n");
8753     }
8754     }
8755     for (i = backwardMostMove; i < upto; i++) {
8756         SendMoveToProgram(i, cps);
8757     }
8758 }
8759
8760
8761 void
8762 ResurrectChessProgram()
8763 {
8764      /* The chess program may have exited.
8765         If so, restart it and feed it all the moves made so far. */
8766
8767     if (appData.noChessProgram || first.pr != NoProc) return;
8768     
8769     StartChessProgram(&first);
8770     InitChessProgram(&first, FALSE);
8771     FeedMovesToProgram(&first, currentMove);
8772
8773     if (!first.sendTime) {
8774         /* can't tell gnuchess what its clock should read,
8775            so we bow to its notion. */
8776         ResetClocks();
8777         timeRemaining[0][currentMove] = whiteTimeRemaining;
8778         timeRemaining[1][currentMove] = blackTimeRemaining;
8779     }
8780
8781     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8782                 appData.icsEngineAnalyze) && first.analysisSupport) {
8783       SendToProgram("analyze\n", &first);
8784       first.analyzing = TRUE;
8785     }
8786 }
8787
8788 /*
8789  * Button procedures
8790  */
8791 void
8792 Reset(redraw, init)
8793      int redraw, init;
8794 {
8795     int i;
8796
8797     if (appData.debugMode) {
8798         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8799                 redraw, init, gameMode);
8800     }
8801     CleanupTail(); // [HGM] vari: delete any stored variations
8802     pausing = pauseExamInvalid = FALSE;
8803     startedFromSetupPosition = blackPlaysFirst = FALSE;
8804     firstMove = TRUE;
8805     whiteFlag = blackFlag = FALSE;
8806     userOfferedDraw = FALSE;
8807     hintRequested = bookRequested = FALSE;
8808     first.maybeThinking = FALSE;
8809     second.maybeThinking = FALSE;
8810     first.bookSuspend = FALSE; // [HGM] book
8811     second.bookSuspend = FALSE;
8812     thinkOutput[0] = NULLCHAR;
8813     lastHint[0] = NULLCHAR;
8814     ClearGameInfo(&gameInfo);
8815     gameInfo.variant = StringToVariant(appData.variant);
8816     ics_user_moved = ics_clock_paused = FALSE;
8817     ics_getting_history = H_FALSE;
8818     ics_gamenum = -1;
8819     white_holding[0] = black_holding[0] = NULLCHAR;
8820     ClearProgramStats();
8821     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8822     
8823     ResetFrontEnd();
8824     ClearHighlights();
8825     flipView = appData.flipView;
8826     ClearPremoveHighlights();
8827     gotPremove = FALSE;
8828     alarmSounded = FALSE;
8829
8830     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8831     if(appData.serverMovesName != NULL) {
8832         /* [HGM] prepare to make moves file for broadcasting */
8833         clock_t t = clock();
8834         if(serverMoves != NULL) fclose(serverMoves);
8835         serverMoves = fopen(appData.serverMovesName, "r");
8836         if(serverMoves != NULL) {
8837             fclose(serverMoves);
8838             /* delay 15 sec before overwriting, so all clients can see end */
8839             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8840         }
8841         serverMoves = fopen(appData.serverMovesName, "w");
8842     }
8843
8844     ExitAnalyzeMode();
8845     gameMode = BeginningOfGame;
8846     ModeHighlight();
8847     if(appData.icsActive) gameInfo.variant = VariantNormal;
8848     currentMove = forwardMostMove = backwardMostMove = 0;
8849     InitPosition(redraw);
8850     for (i = 0; i < MAX_MOVES; i++) {
8851         if (commentList[i] != NULL) {
8852             free(commentList[i]);
8853             commentList[i] = NULL;
8854         }
8855     }
8856     ResetClocks();
8857     timeRemaining[0][0] = whiteTimeRemaining;
8858     timeRemaining[1][0] = blackTimeRemaining;
8859     if (first.pr == NULL) {
8860         StartChessProgram(&first);
8861     }
8862     if (init) {
8863             InitChessProgram(&first, startedFromSetupPosition);
8864     }
8865     DisplayTitle("");
8866     DisplayMessage("", "");
8867     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8868     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8869 }
8870
8871 void
8872 AutoPlayGameLoop()
8873 {
8874     for (;;) {
8875         if (!AutoPlayOneMove())
8876           return;
8877         if (matchMode || appData.timeDelay == 0)
8878           continue;
8879         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8880           return;
8881         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8882         break;
8883     }
8884 }
8885
8886
8887 int
8888 AutoPlayOneMove()
8889 {
8890     int fromX, fromY, toX, toY;
8891
8892     if (appData.debugMode) {
8893       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8894     }
8895
8896     if (gameMode != PlayFromGameFile)
8897       return FALSE;
8898
8899     if (currentMove >= forwardMostMove) {
8900       gameMode = EditGame;
8901       ModeHighlight();
8902
8903       /* [AS] Clear current move marker at the end of a game */
8904       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8905
8906       return FALSE;
8907     }
8908     
8909     toX = moveList[currentMove][2] - AAA;
8910     toY = moveList[currentMove][3] - ONE;
8911
8912     if (moveList[currentMove][1] == '@') {
8913         if (appData.highlightLastMove) {
8914             SetHighlights(-1, -1, toX, toY);
8915         }
8916     } else {
8917         fromX = moveList[currentMove][0] - AAA;
8918         fromY = moveList[currentMove][1] - ONE;
8919
8920         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8921
8922         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8923
8924         if (appData.highlightLastMove) {
8925             SetHighlights(fromX, fromY, toX, toY);
8926         }
8927     }
8928     DisplayMove(currentMove);
8929     SendMoveToProgram(currentMove++, &first);
8930     DisplayBothClocks();
8931     DrawPosition(FALSE, boards[currentMove]);
8932     // [HGM] PV info: always display, routine tests if empty
8933     DisplayComment(currentMove - 1, commentList[currentMove]);
8934     return TRUE;
8935 }
8936
8937
8938 int
8939 LoadGameOneMove(readAhead)
8940      ChessMove readAhead;
8941 {
8942     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8943     char promoChar = NULLCHAR;
8944     ChessMove moveType;
8945     char move[MSG_SIZ];
8946     char *p, *q;
8947     
8948     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8949         gameMode != AnalyzeMode && gameMode != Training) {
8950         gameFileFP = NULL;
8951         return FALSE;
8952     }
8953     
8954     yyboardindex = forwardMostMove;
8955     if (readAhead != (ChessMove)0) {
8956       moveType = readAhead;
8957     } else {
8958       if (gameFileFP == NULL)
8959           return FALSE;
8960       moveType = (ChessMove) yylex();
8961     }
8962     
8963     done = FALSE;
8964     switch (moveType) {
8965       case Comment:
8966         if (appData.debugMode) 
8967           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8968         p = yy_text;
8969
8970         /* append the comment but don't display it */
8971         AppendComment(currentMove, p, FALSE);
8972         return TRUE;
8973
8974       case WhiteCapturesEnPassant:
8975       case BlackCapturesEnPassant:
8976       case WhitePromotionChancellor:
8977       case BlackPromotionChancellor:
8978       case WhitePromotionArchbishop:
8979       case BlackPromotionArchbishop:
8980       case WhitePromotionCentaur:
8981       case BlackPromotionCentaur:
8982       case WhitePromotionQueen:
8983       case BlackPromotionQueen:
8984       case WhitePromotionRook:
8985       case BlackPromotionRook:
8986       case WhitePromotionBishop:
8987       case BlackPromotionBishop:
8988       case WhitePromotionKnight:
8989       case BlackPromotionKnight:
8990       case WhitePromotionKing:
8991       case BlackPromotionKing:
8992       case NormalMove:
8993       case WhiteKingSideCastle:
8994       case WhiteQueenSideCastle:
8995       case BlackKingSideCastle:
8996       case BlackQueenSideCastle:
8997       case WhiteKingSideCastleWild:
8998       case WhiteQueenSideCastleWild:
8999       case BlackKingSideCastleWild:
9000       case BlackQueenSideCastleWild:
9001       /* PUSH Fabien */
9002       case WhiteHSideCastleFR:
9003       case WhiteASideCastleFR:
9004       case BlackHSideCastleFR:
9005       case BlackASideCastleFR:
9006       /* POP Fabien */
9007         if (appData.debugMode)
9008           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9009         fromX = currentMoveString[0] - AAA;
9010         fromY = currentMoveString[1] - ONE;
9011         toX = currentMoveString[2] - AAA;
9012         toY = currentMoveString[3] - ONE;
9013         promoChar = currentMoveString[4];
9014         break;
9015
9016       case WhiteDrop:
9017       case BlackDrop:
9018         if (appData.debugMode)
9019           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9020         fromX = moveType == WhiteDrop ?
9021           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9022         (int) CharToPiece(ToLower(currentMoveString[0]));
9023         fromY = DROP_RANK;
9024         toX = currentMoveString[2] - AAA;
9025         toY = currentMoveString[3] - ONE;
9026         break;
9027
9028       case WhiteWins:
9029       case BlackWins:
9030       case GameIsDrawn:
9031       case GameUnfinished:
9032         if (appData.debugMode)
9033           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9034         p = strchr(yy_text, '{');
9035         if (p == NULL) p = strchr(yy_text, '(');
9036         if (p == NULL) {
9037             p = yy_text;
9038             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9039         } else {
9040             q = strchr(p, *p == '{' ? '}' : ')');
9041             if (q != NULL) *q = NULLCHAR;
9042             p++;
9043         }
9044         GameEnds(moveType, p, GE_FILE);
9045         done = TRUE;
9046         if (cmailMsgLoaded) {
9047             ClearHighlights();
9048             flipView = WhiteOnMove(currentMove);
9049             if (moveType == GameUnfinished) flipView = !flipView;
9050             if (appData.debugMode)
9051               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9052         }
9053         break;
9054
9055       case (ChessMove) 0:       /* end of file */
9056         if (appData.debugMode)
9057           fprintf(debugFP, "Parser hit end of file\n");
9058         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9059           case MT_NONE:
9060           case MT_CHECK:
9061             break;
9062           case MT_CHECKMATE:
9063           case MT_STAINMATE:
9064             if (WhiteOnMove(currentMove)) {
9065                 GameEnds(BlackWins, "Black mates", GE_FILE);
9066             } else {
9067                 GameEnds(WhiteWins, "White mates", GE_FILE);
9068             }
9069             break;
9070           case MT_STALEMATE:
9071             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9072             break;
9073         }
9074         done = TRUE;
9075         break;
9076
9077       case MoveNumberOne:
9078         if (lastLoadGameStart == GNUChessGame) {
9079             /* GNUChessGames have numbers, but they aren't move numbers */
9080             if (appData.debugMode)
9081               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9082                       yy_text, (int) moveType);
9083             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9084         }
9085         /* else fall thru */
9086
9087       case XBoardGame:
9088       case GNUChessGame:
9089       case PGNTag:
9090         /* Reached start of next game in file */
9091         if (appData.debugMode)
9092           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9093         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9094           case MT_NONE:
9095           case MT_CHECK:
9096             break;
9097           case MT_CHECKMATE:
9098           case MT_STAINMATE:
9099             if (WhiteOnMove(currentMove)) {
9100                 GameEnds(BlackWins, "Black mates", GE_FILE);
9101             } else {
9102                 GameEnds(WhiteWins, "White mates", GE_FILE);
9103             }
9104             break;
9105           case MT_STALEMATE:
9106             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9107             break;
9108         }
9109         done = TRUE;
9110         break;
9111
9112       case PositionDiagram:     /* should not happen; ignore */
9113       case ElapsedTime:         /* ignore */
9114       case NAG:                 /* ignore */
9115         if (appData.debugMode)
9116           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9117                   yy_text, (int) moveType);
9118         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9119
9120       case IllegalMove:
9121         if (appData.testLegality) {
9122             if (appData.debugMode)
9123               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9124             sprintf(move, _("Illegal move: %d.%s%s"),
9125                     (forwardMostMove / 2) + 1,
9126                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9127             DisplayError(move, 0);
9128             done = TRUE;
9129         } else {
9130             if (appData.debugMode)
9131               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9132                       yy_text, currentMoveString);
9133             fromX = currentMoveString[0] - AAA;
9134             fromY = currentMoveString[1] - ONE;
9135             toX = currentMoveString[2] - AAA;
9136             toY = currentMoveString[3] - ONE;
9137             promoChar = currentMoveString[4];
9138         }
9139         break;
9140
9141       case AmbiguousMove:
9142         if (appData.debugMode)
9143           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9144         sprintf(move, _("Ambiguous move: %d.%s%s"),
9145                 (forwardMostMove / 2) + 1,
9146                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9147         DisplayError(move, 0);
9148         done = TRUE;
9149         break;
9150
9151       default:
9152       case ImpossibleMove:
9153         if (appData.debugMode)
9154           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9155         sprintf(move, _("Illegal move: %d.%s%s"),
9156                 (forwardMostMove / 2) + 1,
9157                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9158         DisplayError(move, 0);
9159         done = TRUE;
9160         break;
9161     }
9162
9163     if (done) {
9164         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9165             DrawPosition(FALSE, boards[currentMove]);
9166             DisplayBothClocks();
9167             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9168               DisplayComment(currentMove - 1, commentList[currentMove]);
9169         }
9170         (void) StopLoadGameTimer();
9171         gameFileFP = NULL;
9172         cmailOldMove = forwardMostMove;
9173         return FALSE;
9174     } else {
9175         /* currentMoveString is set as a side-effect of yylex */
9176         strcat(currentMoveString, "\n");
9177         strcpy(moveList[forwardMostMove], currentMoveString);
9178         
9179         thinkOutput[0] = NULLCHAR;
9180         MakeMove(fromX, fromY, toX, toY, promoChar);
9181         currentMove = forwardMostMove;
9182         return TRUE;
9183     }
9184 }
9185
9186 /* Load the nth game from the given file */
9187 int
9188 LoadGameFromFile(filename, n, title, useList)
9189      char *filename;
9190      int n;
9191      char *title;
9192      /*Boolean*/ int useList;
9193 {
9194     FILE *f;
9195     char buf[MSG_SIZ];
9196
9197     if (strcmp(filename, "-") == 0) {
9198         f = stdin;
9199         title = "stdin";
9200     } else {
9201         f = fopen(filename, "rb");
9202         if (f == NULL) {
9203           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9204             DisplayError(buf, errno);
9205             return FALSE;
9206         }
9207     }
9208     if (fseek(f, 0, 0) == -1) {
9209         /* f is not seekable; probably a pipe */
9210         useList = FALSE;
9211     }
9212     if (useList && n == 0) {
9213         int error = GameListBuild(f);
9214         if (error) {
9215             DisplayError(_("Cannot build game list"), error);
9216         } else if (!ListEmpty(&gameList) &&
9217                    ((ListGame *) gameList.tailPred)->number > 1) {
9218             GameListPopUp(f, title);
9219             return TRUE;
9220         }
9221         GameListDestroy();
9222         n = 1;
9223     }
9224     if (n == 0) n = 1;
9225     return LoadGame(f, n, title, FALSE);
9226 }
9227
9228
9229 void
9230 MakeRegisteredMove()
9231 {
9232     int fromX, fromY, toX, toY;
9233     char promoChar;
9234     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9235         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9236           case CMAIL_MOVE:
9237           case CMAIL_DRAW:
9238             if (appData.debugMode)
9239               fprintf(debugFP, "Restoring %s for game %d\n",
9240                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9241     
9242             thinkOutput[0] = NULLCHAR;
9243             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9244             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9245             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9246             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9247             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9248             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9249             MakeMove(fromX, fromY, toX, toY, promoChar);
9250             ShowMove(fromX, fromY, toX, toY);
9251               
9252             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9253               case MT_NONE:
9254               case MT_CHECK:
9255                 break;
9256                 
9257               case MT_CHECKMATE:
9258               case MT_STAINMATE:
9259                 if (WhiteOnMove(currentMove)) {
9260                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9261                 } else {
9262                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9263                 }
9264                 break;
9265                 
9266               case MT_STALEMATE:
9267                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9268                 break;
9269             }
9270
9271             break;
9272             
9273           case CMAIL_RESIGN:
9274             if (WhiteOnMove(currentMove)) {
9275                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9276             } else {
9277                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9278             }
9279             break;
9280             
9281           case CMAIL_ACCEPT:
9282             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9283             break;
9284               
9285           default:
9286             break;
9287         }
9288     }
9289
9290     return;
9291 }
9292
9293 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9294 int
9295 CmailLoadGame(f, gameNumber, title, useList)
9296      FILE *f;
9297      int gameNumber;
9298      char *title;
9299      int useList;
9300 {
9301     int retVal;
9302
9303     if (gameNumber > nCmailGames) {
9304         DisplayError(_("No more games in this message"), 0);
9305         return FALSE;
9306     }
9307     if (f == lastLoadGameFP) {
9308         int offset = gameNumber - lastLoadGameNumber;
9309         if (offset == 0) {
9310             cmailMsg[0] = NULLCHAR;
9311             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9312                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9313                 nCmailMovesRegistered--;
9314             }
9315             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9316             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9317                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9318             }
9319         } else {
9320             if (! RegisterMove()) return FALSE;
9321         }
9322     }
9323
9324     retVal = LoadGame(f, gameNumber, title, useList);
9325
9326     /* Make move registered during previous look at this game, if any */
9327     MakeRegisteredMove();
9328
9329     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9330         commentList[currentMove]
9331           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9332         DisplayComment(currentMove - 1, commentList[currentMove]);
9333     }
9334
9335     return retVal;
9336 }
9337
9338 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9339 int
9340 ReloadGame(offset)
9341      int offset;
9342 {
9343     int gameNumber = lastLoadGameNumber + offset;
9344     if (lastLoadGameFP == NULL) {
9345         DisplayError(_("No game has been loaded yet"), 0);
9346         return FALSE;
9347     }
9348     if (gameNumber <= 0) {
9349         DisplayError(_("Can't back up any further"), 0);
9350         return FALSE;
9351     }
9352     if (cmailMsgLoaded) {
9353         return CmailLoadGame(lastLoadGameFP, gameNumber,
9354                              lastLoadGameTitle, lastLoadGameUseList);
9355     } else {
9356         return LoadGame(lastLoadGameFP, gameNumber,
9357                         lastLoadGameTitle, lastLoadGameUseList);
9358     }
9359 }
9360
9361
9362
9363 /* Load the nth game from open file f */
9364 int
9365 LoadGame(f, gameNumber, title, useList)
9366      FILE *f;
9367      int gameNumber;
9368      char *title;
9369      int useList;
9370 {
9371     ChessMove cm;
9372     char buf[MSG_SIZ];
9373     int gn = gameNumber;
9374     ListGame *lg = NULL;
9375     int numPGNTags = 0;
9376     int err;
9377     GameMode oldGameMode;
9378     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9379
9380     if (appData.debugMode) 
9381         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9382
9383     if (gameMode == Training )
9384         SetTrainingModeOff();
9385
9386     oldGameMode = gameMode;
9387     if (gameMode != BeginningOfGame) {
9388       Reset(FALSE, TRUE);
9389     }
9390
9391     gameFileFP = f;
9392     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9393         fclose(lastLoadGameFP);
9394     }
9395
9396     if (useList) {
9397         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9398         
9399         if (lg) {
9400             fseek(f, lg->offset, 0);
9401             GameListHighlight(gameNumber);
9402             gn = 1;
9403         }
9404         else {
9405             DisplayError(_("Game number out of range"), 0);
9406             return FALSE;
9407         }
9408     } else {
9409         GameListDestroy();
9410         if (fseek(f, 0, 0) == -1) {
9411             if (f == lastLoadGameFP ?
9412                 gameNumber == lastLoadGameNumber + 1 :
9413                 gameNumber == 1) {
9414                 gn = 1;
9415             } else {
9416                 DisplayError(_("Can't seek on game file"), 0);
9417                 return FALSE;
9418             }
9419         }
9420     }
9421     lastLoadGameFP = f;
9422     lastLoadGameNumber = gameNumber;
9423     strcpy(lastLoadGameTitle, title);
9424     lastLoadGameUseList = useList;
9425
9426     yynewfile(f);
9427
9428     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9429       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9430                 lg->gameInfo.black);
9431             DisplayTitle(buf);
9432     } else if (*title != NULLCHAR) {
9433         if (gameNumber > 1) {
9434             sprintf(buf, "%s %d", title, gameNumber);
9435             DisplayTitle(buf);
9436         } else {
9437             DisplayTitle(title);
9438         }
9439     }
9440
9441     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9442         gameMode = PlayFromGameFile;
9443         ModeHighlight();
9444     }
9445
9446     currentMove = forwardMostMove = backwardMostMove = 0;
9447     CopyBoard(boards[0], initialPosition);
9448     StopClocks();
9449
9450     /*
9451      * Skip the first gn-1 games in the file.
9452      * Also skip over anything that precedes an identifiable 
9453      * start of game marker, to avoid being confused by 
9454      * garbage at the start of the file.  Currently 
9455      * recognized start of game markers are the move number "1",
9456      * the pattern "gnuchess .* game", the pattern
9457      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9458      * A game that starts with one of the latter two patterns
9459      * will also have a move number 1, possibly
9460      * following a position diagram.
9461      * 5-4-02: Let's try being more lenient and allowing a game to
9462      * start with an unnumbered move.  Does that break anything?
9463      */
9464     cm = lastLoadGameStart = (ChessMove) 0;
9465     while (gn > 0) {
9466         yyboardindex = forwardMostMove;
9467         cm = (ChessMove) yylex();
9468         switch (cm) {
9469           case (ChessMove) 0:
9470             if (cmailMsgLoaded) {
9471                 nCmailGames = CMAIL_MAX_GAMES - gn;
9472             } else {
9473                 Reset(TRUE, TRUE);
9474                 DisplayError(_("Game not found in file"), 0);
9475             }
9476             return FALSE;
9477
9478           case GNUChessGame:
9479           case XBoardGame:
9480             gn--;
9481             lastLoadGameStart = cm;
9482             break;
9483             
9484           case MoveNumberOne:
9485             switch (lastLoadGameStart) {
9486               case GNUChessGame:
9487               case XBoardGame:
9488               case PGNTag:
9489                 break;
9490               case MoveNumberOne:
9491               case (ChessMove) 0:
9492                 gn--;           /* count this game */
9493                 lastLoadGameStart = cm;
9494                 break;
9495               default:
9496                 /* impossible */
9497                 break;
9498             }
9499             break;
9500
9501           case PGNTag:
9502             switch (lastLoadGameStart) {
9503               case GNUChessGame:
9504               case PGNTag:
9505               case MoveNumberOne:
9506               case (ChessMove) 0:
9507                 gn--;           /* count this game */
9508                 lastLoadGameStart = cm;
9509                 break;
9510               case XBoardGame:
9511                 lastLoadGameStart = cm; /* game counted already */
9512                 break;
9513               default:
9514                 /* impossible */
9515                 break;
9516             }
9517             if (gn > 0) {
9518                 do {
9519                     yyboardindex = forwardMostMove;
9520                     cm = (ChessMove) yylex();
9521                 } while (cm == PGNTag || cm == Comment);
9522             }
9523             break;
9524
9525           case WhiteWins:
9526           case BlackWins:
9527           case GameIsDrawn:
9528             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9529                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9530                     != CMAIL_OLD_RESULT) {
9531                     nCmailResults ++ ;
9532                     cmailResult[  CMAIL_MAX_GAMES
9533                                 - gn - 1] = CMAIL_OLD_RESULT;
9534                 }
9535             }
9536             break;
9537
9538           case NormalMove:
9539             /* Only a NormalMove can be at the start of a game
9540              * without a position diagram. */
9541             if (lastLoadGameStart == (ChessMove) 0) {
9542               gn--;
9543               lastLoadGameStart = MoveNumberOne;
9544             }
9545             break;
9546
9547           default:
9548             break;
9549         }
9550     }
9551     
9552     if (appData.debugMode)
9553       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9554
9555     if (cm == XBoardGame) {
9556         /* Skip any header junk before position diagram and/or move 1 */
9557         for (;;) {
9558             yyboardindex = forwardMostMove;
9559             cm = (ChessMove) yylex();
9560
9561             if (cm == (ChessMove) 0 ||
9562                 cm == GNUChessGame || cm == XBoardGame) {
9563                 /* Empty game; pretend end-of-file and handle later */
9564                 cm = (ChessMove) 0;
9565                 break;
9566             }
9567
9568             if (cm == MoveNumberOne || cm == PositionDiagram ||
9569                 cm == PGNTag || cm == Comment)
9570               break;
9571         }
9572     } else if (cm == GNUChessGame) {
9573         if (gameInfo.event != NULL) {
9574             free(gameInfo.event);
9575         }
9576         gameInfo.event = StrSave(yy_text);
9577     }   
9578
9579     startedFromSetupPosition = FALSE;
9580     while (cm == PGNTag) {
9581         if (appData.debugMode) 
9582           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9583         err = ParsePGNTag(yy_text, &gameInfo);
9584         if (!err) numPGNTags++;
9585
9586         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9587         if(gameInfo.variant != oldVariant) {
9588             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9589             InitPosition(TRUE);
9590             oldVariant = gameInfo.variant;
9591             if (appData.debugMode) 
9592               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9593         }
9594
9595
9596         if (gameInfo.fen != NULL) {
9597           Board initial_position;
9598           startedFromSetupPosition = TRUE;
9599           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9600             Reset(TRUE, TRUE);
9601             DisplayError(_("Bad FEN position in file"), 0);
9602             return FALSE;
9603           }
9604           CopyBoard(boards[0], initial_position);
9605           if (blackPlaysFirst) {
9606             currentMove = forwardMostMove = backwardMostMove = 1;
9607             CopyBoard(boards[1], initial_position);
9608             strcpy(moveList[0], "");
9609             strcpy(parseList[0], "");
9610             timeRemaining[0][1] = whiteTimeRemaining;
9611             timeRemaining[1][1] = blackTimeRemaining;
9612             if (commentList[0] != NULL) {
9613               commentList[1] = commentList[0];
9614               commentList[0] = NULL;
9615             }
9616           } else {
9617             currentMove = forwardMostMove = backwardMostMove = 0;
9618           }
9619           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9620           {   int i;
9621               initialRulePlies = FENrulePlies;
9622               for( i=0; i< nrCastlingRights; i++ )
9623                   initialRights[i] = initial_position[CASTLING][i];
9624           }
9625           yyboardindex = forwardMostMove;
9626           free(gameInfo.fen);
9627           gameInfo.fen = NULL;
9628         }
9629
9630         yyboardindex = forwardMostMove;
9631         cm = (ChessMove) yylex();
9632
9633         /* Handle comments interspersed among the tags */
9634         while (cm == Comment) {
9635             char *p;
9636             if (appData.debugMode) 
9637               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9638             p = yy_text;
9639             AppendComment(currentMove, p, FALSE);
9640             yyboardindex = forwardMostMove;
9641             cm = (ChessMove) yylex();
9642         }
9643     }
9644
9645     /* don't rely on existence of Event tag since if game was
9646      * pasted from clipboard the Event tag may not exist
9647      */
9648     if (numPGNTags > 0){
9649         char *tags;
9650         if (gameInfo.variant == VariantNormal) {
9651           gameInfo.variant = StringToVariant(gameInfo.event);
9652         }
9653         if (!matchMode) {
9654           if( appData.autoDisplayTags ) {
9655             tags = PGNTags(&gameInfo);
9656             TagsPopUp(tags, CmailMsg());
9657             free(tags);
9658           }
9659         }
9660     } else {
9661         /* Make something up, but don't display it now */
9662         SetGameInfo();
9663         TagsPopDown();
9664     }
9665
9666     if (cm == PositionDiagram) {
9667         int i, j;
9668         char *p;
9669         Board initial_position;
9670
9671         if (appData.debugMode)
9672           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9673
9674         if (!startedFromSetupPosition) {
9675             p = yy_text;
9676             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9677               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9678                 switch (*p) {
9679                   case '[':
9680                   case '-':
9681                   case ' ':
9682                   case '\t':
9683                   case '\n':
9684                   case '\r':
9685                     break;
9686                   default:
9687                     initial_position[i][j++] = CharToPiece(*p);
9688                     break;
9689                 }
9690             while (*p == ' ' || *p == '\t' ||
9691                    *p == '\n' || *p == '\r') p++;
9692         
9693             if (strncmp(p, "black", strlen("black"))==0)
9694               blackPlaysFirst = TRUE;
9695             else
9696               blackPlaysFirst = FALSE;
9697             startedFromSetupPosition = TRUE;
9698         
9699             CopyBoard(boards[0], initial_position);
9700             if (blackPlaysFirst) {
9701                 currentMove = forwardMostMove = backwardMostMove = 1;
9702                 CopyBoard(boards[1], initial_position);
9703                 strcpy(moveList[0], "");
9704                 strcpy(parseList[0], "");
9705                 timeRemaining[0][1] = whiteTimeRemaining;
9706                 timeRemaining[1][1] = blackTimeRemaining;
9707                 if (commentList[0] != NULL) {
9708                     commentList[1] = commentList[0];
9709                     commentList[0] = NULL;
9710                 }
9711             } else {
9712                 currentMove = forwardMostMove = backwardMostMove = 0;
9713             }
9714         }
9715         yyboardindex = forwardMostMove;
9716         cm = (ChessMove) yylex();
9717     }
9718
9719     if (first.pr == NoProc) {
9720         StartChessProgram(&first);
9721     }
9722     InitChessProgram(&first, FALSE);
9723     SendToProgram("force\n", &first);
9724     if (startedFromSetupPosition) {
9725         SendBoard(&first, forwardMostMove);
9726     if (appData.debugMode) {
9727         fprintf(debugFP, "Load Game\n");
9728     }
9729         DisplayBothClocks();
9730     }      
9731
9732     /* [HGM] server: flag to write setup moves in broadcast file as one */
9733     loadFlag = appData.suppressLoadMoves;
9734
9735     while (cm == Comment) {
9736         char *p;
9737         if (appData.debugMode) 
9738           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9739         p = yy_text;
9740         AppendComment(currentMove, p, FALSE);
9741         yyboardindex = forwardMostMove;
9742         cm = (ChessMove) yylex();
9743     }
9744
9745     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9746         cm == WhiteWins || cm == BlackWins ||
9747         cm == GameIsDrawn || cm == GameUnfinished) {
9748         DisplayMessage("", _("No moves in game"));
9749         if (cmailMsgLoaded) {
9750             if (appData.debugMode)
9751               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9752             ClearHighlights();
9753             flipView = FALSE;
9754         }
9755         DrawPosition(FALSE, boards[currentMove]);
9756         DisplayBothClocks();
9757         gameMode = EditGame;
9758         ModeHighlight();
9759         gameFileFP = NULL;
9760         cmailOldMove = 0;
9761         return TRUE;
9762     }
9763
9764     // [HGM] PV info: routine tests if comment empty
9765     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9766         DisplayComment(currentMove - 1, commentList[currentMove]);
9767     }
9768     if (!matchMode && appData.timeDelay != 0) 
9769       DrawPosition(FALSE, boards[currentMove]);
9770
9771     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9772       programStats.ok_to_send = 1;
9773     }
9774
9775     /* if the first token after the PGN tags is a move
9776      * and not move number 1, retrieve it from the parser 
9777      */
9778     if (cm != MoveNumberOne)
9779         LoadGameOneMove(cm);
9780
9781     /* load the remaining moves from the file */
9782     while (LoadGameOneMove((ChessMove)0)) {
9783       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9784       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9785     }
9786
9787     /* rewind to the start of the game */
9788     currentMove = backwardMostMove;
9789
9790     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9791
9792     if (oldGameMode == AnalyzeFile ||
9793         oldGameMode == AnalyzeMode) {
9794       AnalyzeFileEvent();
9795     }
9796
9797     if (matchMode || appData.timeDelay == 0) {
9798       ToEndEvent();
9799       gameMode = EditGame;
9800       ModeHighlight();
9801     } else if (appData.timeDelay > 0) {
9802       AutoPlayGameLoop();
9803     }
9804
9805     if (appData.debugMode) 
9806         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9807
9808     loadFlag = 0; /* [HGM] true game starts */
9809     return TRUE;
9810 }
9811
9812 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9813 int
9814 ReloadPosition(offset)
9815      int offset;
9816 {
9817     int positionNumber = lastLoadPositionNumber + offset;
9818     if (lastLoadPositionFP == NULL) {
9819         DisplayError(_("No position has been loaded yet"), 0);
9820         return FALSE;
9821     }
9822     if (positionNumber <= 0) {
9823         DisplayError(_("Can't back up any further"), 0);
9824         return FALSE;
9825     }
9826     return LoadPosition(lastLoadPositionFP, positionNumber,
9827                         lastLoadPositionTitle);
9828 }
9829
9830 /* Load the nth position from the given file */
9831 int
9832 LoadPositionFromFile(filename, n, title)
9833      char *filename;
9834      int n;
9835      char *title;
9836 {
9837     FILE *f;
9838     char buf[MSG_SIZ];
9839
9840     if (strcmp(filename, "-") == 0) {
9841         return LoadPosition(stdin, n, "stdin");
9842     } else {
9843         f = fopen(filename, "rb");
9844         if (f == NULL) {
9845             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9846             DisplayError(buf, errno);
9847             return FALSE;
9848         } else {
9849             return LoadPosition(f, n, title);
9850         }
9851     }
9852 }
9853
9854 /* Load the nth position from the given open file, and close it */
9855 int
9856 LoadPosition(f, positionNumber, title)
9857      FILE *f;
9858      int positionNumber;
9859      char *title;
9860 {
9861     char *p, line[MSG_SIZ];
9862     Board initial_position;
9863     int i, j, fenMode, pn;
9864     
9865     if (gameMode == Training )
9866         SetTrainingModeOff();
9867
9868     if (gameMode != BeginningOfGame) {
9869         Reset(FALSE, TRUE);
9870     }
9871     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9872         fclose(lastLoadPositionFP);
9873     }
9874     if (positionNumber == 0) positionNumber = 1;
9875     lastLoadPositionFP = f;
9876     lastLoadPositionNumber = positionNumber;
9877     strcpy(lastLoadPositionTitle, title);
9878     if (first.pr == NoProc) {
9879       StartChessProgram(&first);
9880       InitChessProgram(&first, FALSE);
9881     }    
9882     pn = positionNumber;
9883     if (positionNumber < 0) {
9884         /* Negative position number means to seek to that byte offset */
9885         if (fseek(f, -positionNumber, 0) == -1) {
9886             DisplayError(_("Can't seek on position file"), 0);
9887             return FALSE;
9888         };
9889         pn = 1;
9890     } else {
9891         if (fseek(f, 0, 0) == -1) {
9892             if (f == lastLoadPositionFP ?
9893                 positionNumber == lastLoadPositionNumber + 1 :
9894                 positionNumber == 1) {
9895                 pn = 1;
9896             } else {
9897                 DisplayError(_("Can't seek on position file"), 0);
9898                 return FALSE;
9899             }
9900         }
9901     }
9902     /* See if this file is FEN or old-style xboard */
9903     if (fgets(line, MSG_SIZ, f) == NULL) {
9904         DisplayError(_("Position not found in file"), 0);
9905         return FALSE;
9906     }
9907     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9908     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9909
9910     if (pn >= 2) {
9911         if (fenMode || line[0] == '#') pn--;
9912         while (pn > 0) {
9913             /* skip positions before number pn */
9914             if (fgets(line, MSG_SIZ, f) == NULL) {
9915                 Reset(TRUE, TRUE);
9916                 DisplayError(_("Position not found in file"), 0);
9917                 return FALSE;
9918             }
9919             if (fenMode || line[0] == '#') pn--;
9920         }
9921     }
9922
9923     if (fenMode) {
9924         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9925             DisplayError(_("Bad FEN position in file"), 0);
9926             return FALSE;
9927         }
9928     } else {
9929         (void) fgets(line, MSG_SIZ, f);
9930         (void) fgets(line, MSG_SIZ, f);
9931     
9932         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9933             (void) fgets(line, MSG_SIZ, f);
9934             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9935                 if (*p == ' ')
9936                   continue;
9937                 initial_position[i][j++] = CharToPiece(*p);
9938             }
9939         }
9940     
9941         blackPlaysFirst = FALSE;
9942         if (!feof(f)) {
9943             (void) fgets(line, MSG_SIZ, f);
9944             if (strncmp(line, "black", strlen("black"))==0)
9945               blackPlaysFirst = TRUE;
9946         }
9947     }
9948     startedFromSetupPosition = TRUE;
9949     
9950     SendToProgram("force\n", &first);
9951     CopyBoard(boards[0], initial_position);
9952     if (blackPlaysFirst) {
9953         currentMove = forwardMostMove = backwardMostMove = 1;
9954         strcpy(moveList[0], "");
9955         strcpy(parseList[0], "");
9956         CopyBoard(boards[1], initial_position);
9957         DisplayMessage("", _("Black to play"));
9958     } else {
9959         currentMove = forwardMostMove = backwardMostMove = 0;
9960         DisplayMessage("", _("White to play"));
9961     }
9962     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9963     SendBoard(&first, forwardMostMove);
9964     if (appData.debugMode) {
9965 int i, j;
9966   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9967   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9968         fprintf(debugFP, "Load Position\n");
9969     }
9970
9971     if (positionNumber > 1) {
9972         sprintf(line, "%s %d", title, positionNumber);
9973         DisplayTitle(line);
9974     } else {
9975         DisplayTitle(title);
9976     }
9977     gameMode = EditGame;
9978     ModeHighlight();
9979     ResetClocks();
9980     timeRemaining[0][1] = whiteTimeRemaining;
9981     timeRemaining[1][1] = blackTimeRemaining;
9982     DrawPosition(FALSE, boards[currentMove]);
9983    
9984     return TRUE;
9985 }
9986
9987
9988 void
9989 CopyPlayerNameIntoFileName(dest, src)
9990      char **dest, *src;
9991 {
9992     while (*src != NULLCHAR && *src != ',') {
9993         if (*src == ' ') {
9994             *(*dest)++ = '_';
9995             src++;
9996         } else {
9997             *(*dest)++ = *src++;
9998         }
9999     }
10000 }
10001
10002 char *DefaultFileName(ext)
10003      char *ext;
10004 {
10005     static char def[MSG_SIZ];
10006     char *p;
10007
10008     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10009         p = def;
10010         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10011         *p++ = '-';
10012         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10013         *p++ = '.';
10014         strcpy(p, ext);
10015     } else {
10016         def[0] = NULLCHAR;
10017     }
10018     return def;
10019 }
10020
10021 /* Save the current game to the given file */
10022 int
10023 SaveGameToFile(filename, append)
10024      char *filename;
10025      int append;
10026 {
10027     FILE *f;
10028     char buf[MSG_SIZ];
10029
10030     if (strcmp(filename, "-") == 0) {
10031         return SaveGame(stdout, 0, NULL);
10032     } else {
10033         f = fopen(filename, append ? "a" : "w");
10034         if (f == NULL) {
10035             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10036             DisplayError(buf, errno);
10037             return FALSE;
10038         } else {
10039             return SaveGame(f, 0, NULL);
10040         }
10041     }
10042 }
10043
10044 char *
10045 SavePart(str)
10046      char *str;
10047 {
10048     static char buf[MSG_SIZ];
10049     char *p;
10050     
10051     p = strchr(str, ' ');
10052     if (p == NULL) return str;
10053     strncpy(buf, str, p - str);
10054     buf[p - str] = NULLCHAR;
10055     return buf;
10056 }
10057
10058 #define PGN_MAX_LINE 75
10059
10060 #define PGN_SIDE_WHITE  0
10061 #define PGN_SIDE_BLACK  1
10062
10063 /* [AS] */
10064 static int FindFirstMoveOutOfBook( int side )
10065 {
10066     int result = -1;
10067
10068     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10069         int index = backwardMostMove;
10070         int has_book_hit = 0;
10071
10072         if( (index % 2) != side ) {
10073             index++;
10074         }
10075
10076         while( index < forwardMostMove ) {
10077             /* Check to see if engine is in book */
10078             int depth = pvInfoList[index].depth;
10079             int score = pvInfoList[index].score;
10080             int in_book = 0;
10081
10082             if( depth <= 2 ) {
10083                 in_book = 1;
10084             }
10085             else if( score == 0 && depth == 63 ) {
10086                 in_book = 1; /* Zappa */
10087             }
10088             else if( score == 2 && depth == 99 ) {
10089                 in_book = 1; /* Abrok */
10090             }
10091
10092             has_book_hit += in_book;
10093
10094             if( ! in_book ) {
10095                 result = index;
10096
10097                 break;
10098             }
10099
10100             index += 2;
10101         }
10102     }
10103
10104     return result;
10105 }
10106
10107 /* [AS] */
10108 void GetOutOfBookInfo( char * buf )
10109 {
10110     int oob[2];
10111     int i;
10112     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10113
10114     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10115     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10116
10117     *buf = '\0';
10118
10119     if( oob[0] >= 0 || oob[1] >= 0 ) {
10120         for( i=0; i<2; i++ ) {
10121             int idx = oob[i];
10122
10123             if( idx >= 0 ) {
10124                 if( i > 0 && oob[0] >= 0 ) {
10125                     strcat( buf, "   " );
10126                 }
10127
10128                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10129                 sprintf( buf+strlen(buf), "%s%.2f", 
10130                     pvInfoList[idx].score >= 0 ? "+" : "",
10131                     pvInfoList[idx].score / 100.0 );
10132             }
10133         }
10134     }
10135 }
10136
10137 /* Save game in PGN style and close the file */
10138 int
10139 SaveGamePGN(f)
10140      FILE *f;
10141 {
10142     int i, offset, linelen, newblock;
10143     time_t tm;
10144 //    char *movetext;
10145     char numtext[32];
10146     int movelen, numlen, blank;
10147     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10148
10149     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10150     
10151     tm = time((time_t *) NULL);
10152     
10153     PrintPGNTags(f, &gameInfo);
10154     
10155     if (backwardMostMove > 0 || startedFromSetupPosition) {
10156         char *fen = PositionToFEN(backwardMostMove, NULL);
10157         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10158         fprintf(f, "\n{--------------\n");
10159         PrintPosition(f, backwardMostMove);
10160         fprintf(f, "--------------}\n");
10161         free(fen);
10162     }
10163     else {
10164         /* [AS] Out of book annotation */
10165         if( appData.saveOutOfBookInfo ) {
10166             char buf[64];
10167
10168             GetOutOfBookInfo( buf );
10169
10170             if( buf[0] != '\0' ) {
10171                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10172             }
10173         }
10174
10175         fprintf(f, "\n");
10176     }
10177
10178     i = backwardMostMove;
10179     linelen = 0;
10180     newblock = TRUE;
10181
10182     while (i < forwardMostMove) {
10183         /* Print comments preceding this move */
10184         if (commentList[i] != NULL) {
10185             if (linelen > 0) fprintf(f, "\n");
10186             fprintf(f, "%s", commentList[i]);
10187             linelen = 0;
10188             newblock = TRUE;
10189         }
10190
10191         /* Format move number */
10192         if ((i % 2) == 0) {
10193             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10194         } else {
10195             if (newblock) {
10196                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10197             } else {
10198                 numtext[0] = NULLCHAR;
10199             }
10200         }
10201         numlen = strlen(numtext);
10202         newblock = FALSE;
10203
10204         /* Print move number */
10205         blank = linelen > 0 && numlen > 0;
10206         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10207             fprintf(f, "\n");
10208             linelen = 0;
10209             blank = 0;
10210         }
10211         if (blank) {
10212             fprintf(f, " ");
10213             linelen++;
10214         }
10215         fprintf(f, "%s", numtext);
10216         linelen += numlen;
10217
10218         /* Get move */
10219         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10220         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10221
10222         /* Print move */
10223         blank = linelen > 0 && movelen > 0;
10224         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10225             fprintf(f, "\n");
10226             linelen = 0;
10227             blank = 0;
10228         }
10229         if (blank) {
10230             fprintf(f, " ");
10231             linelen++;
10232         }
10233         fprintf(f, "%s", move_buffer);
10234         linelen += movelen;
10235
10236         /* [AS] Add PV info if present */
10237         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10238             /* [HGM] add time */
10239             char buf[MSG_SIZ]; int seconds;
10240
10241             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10242
10243             if( seconds <= 0) buf[0] = 0; else
10244             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10245                 seconds = (seconds + 4)/10; // round to full seconds
10246                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10247                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10248             }
10249
10250             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10251                 pvInfoList[i].score >= 0 ? "+" : "",
10252                 pvInfoList[i].score / 100.0,
10253                 pvInfoList[i].depth,
10254                 buf );
10255
10256             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10257
10258             /* Print score/depth */
10259             blank = linelen > 0 && movelen > 0;
10260             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10261                 fprintf(f, "\n");
10262                 linelen = 0;
10263                 blank = 0;
10264             }
10265             if (blank) {
10266                 fprintf(f, " ");
10267                 linelen++;
10268             }
10269             fprintf(f, "%s", move_buffer);
10270             linelen += movelen;
10271         }
10272
10273         i++;
10274     }
10275     
10276     /* Start a new line */
10277     if (linelen > 0) fprintf(f, "\n");
10278
10279     /* Print comments after last move */
10280     if (commentList[i] != NULL) {
10281         fprintf(f, "%s\n", commentList[i]);
10282     }
10283
10284     /* Print result */
10285     if (gameInfo.resultDetails != NULL &&
10286         gameInfo.resultDetails[0] != NULLCHAR) {
10287         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10288                 PGNResult(gameInfo.result));
10289     } else {
10290         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10291     }
10292
10293     fclose(f);
10294     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10295     return TRUE;
10296 }
10297
10298 /* Save game in old style and close the file */
10299 int
10300 SaveGameOldStyle(f)
10301      FILE *f;
10302 {
10303     int i, offset;
10304     time_t tm;
10305     
10306     tm = time((time_t *) NULL);
10307     
10308     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10309     PrintOpponents(f);
10310     
10311     if (backwardMostMove > 0 || startedFromSetupPosition) {
10312         fprintf(f, "\n[--------------\n");
10313         PrintPosition(f, backwardMostMove);
10314         fprintf(f, "--------------]\n");
10315     } else {
10316         fprintf(f, "\n");
10317     }
10318
10319     i = backwardMostMove;
10320     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10321
10322     while (i < forwardMostMove) {
10323         if (commentList[i] != NULL) {
10324             fprintf(f, "[%s]\n", commentList[i]);
10325         }
10326
10327         if ((i % 2) == 1) {
10328             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10329             i++;
10330         } else {
10331             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10332             i++;
10333             if (commentList[i] != NULL) {
10334                 fprintf(f, "\n");
10335                 continue;
10336             }
10337             if (i >= forwardMostMove) {
10338                 fprintf(f, "\n");
10339                 break;
10340             }
10341             fprintf(f, "%s\n", parseList[i]);
10342             i++;
10343         }
10344     }
10345     
10346     if (commentList[i] != NULL) {
10347         fprintf(f, "[%s]\n", commentList[i]);
10348     }
10349
10350     /* This isn't really the old style, but it's close enough */
10351     if (gameInfo.resultDetails != NULL &&
10352         gameInfo.resultDetails[0] != NULLCHAR) {
10353         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10354                 gameInfo.resultDetails);
10355     } else {
10356         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10357     }
10358
10359     fclose(f);
10360     return TRUE;
10361 }
10362
10363 /* Save the current game to open file f and close the file */
10364 int
10365 SaveGame(f, dummy, dummy2)
10366      FILE *f;
10367      int dummy;
10368      char *dummy2;
10369 {
10370     if (gameMode == EditPosition) EditPositionDone(TRUE);
10371     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10372     if (appData.oldSaveStyle)
10373       return SaveGameOldStyle(f);
10374     else
10375       return SaveGamePGN(f);
10376 }
10377
10378 /* Save the current position to the given file */
10379 int
10380 SavePositionToFile(filename)
10381      char *filename;
10382 {
10383     FILE *f;
10384     char buf[MSG_SIZ];
10385
10386     if (strcmp(filename, "-") == 0) {
10387         return SavePosition(stdout, 0, NULL);
10388     } else {
10389         f = fopen(filename, "a");
10390         if (f == NULL) {
10391             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10392             DisplayError(buf, errno);
10393             return FALSE;
10394         } else {
10395             SavePosition(f, 0, NULL);
10396             return TRUE;
10397         }
10398     }
10399 }
10400
10401 /* Save the current position to the given open file and close the file */
10402 int
10403 SavePosition(f, dummy, dummy2)
10404      FILE *f;
10405      int dummy;
10406      char *dummy2;
10407 {
10408     time_t tm;
10409     char *fen;
10410     
10411     if (gameMode == EditPosition) EditPositionDone(TRUE);
10412     if (appData.oldSaveStyle) {
10413         tm = time((time_t *) NULL);
10414     
10415         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10416         PrintOpponents(f);
10417         fprintf(f, "[--------------\n");
10418         PrintPosition(f, currentMove);
10419         fprintf(f, "--------------]\n");
10420     } else {
10421         fen = PositionToFEN(currentMove, NULL);
10422         fprintf(f, "%s\n", fen);
10423         free(fen);
10424     }
10425     fclose(f);
10426     return TRUE;
10427 }
10428
10429 void
10430 ReloadCmailMsgEvent(unregister)
10431      int unregister;
10432 {
10433 #if !WIN32
10434     static char *inFilename = NULL;
10435     static char *outFilename;
10436     int i;
10437     struct stat inbuf, outbuf;
10438     int status;
10439     
10440     /* Any registered moves are unregistered if unregister is set, */
10441     /* i.e. invoked by the signal handler */
10442     if (unregister) {
10443         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10444             cmailMoveRegistered[i] = FALSE;
10445             if (cmailCommentList[i] != NULL) {
10446                 free(cmailCommentList[i]);
10447                 cmailCommentList[i] = NULL;
10448             }
10449         }
10450         nCmailMovesRegistered = 0;
10451     }
10452
10453     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10454         cmailResult[i] = CMAIL_NOT_RESULT;
10455     }
10456     nCmailResults = 0;
10457
10458     if (inFilename == NULL) {
10459         /* Because the filenames are static they only get malloced once  */
10460         /* and they never get freed                                      */
10461         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10462         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10463
10464         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10465         sprintf(outFilename, "%s.out", appData.cmailGameName);
10466     }
10467     
10468     status = stat(outFilename, &outbuf);
10469     if (status < 0) {
10470         cmailMailedMove = FALSE;
10471     } else {
10472         status = stat(inFilename, &inbuf);
10473         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10474     }
10475     
10476     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10477        counts the games, notes how each one terminated, etc.
10478        
10479        It would be nice to remove this kludge and instead gather all
10480        the information while building the game list.  (And to keep it
10481        in the game list nodes instead of having a bunch of fixed-size
10482        parallel arrays.)  Note this will require getting each game's
10483        termination from the PGN tags, as the game list builder does
10484        not process the game moves.  --mann
10485        */
10486     cmailMsgLoaded = TRUE;
10487     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10488     
10489     /* Load first game in the file or popup game menu */
10490     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10491
10492 #endif /* !WIN32 */
10493     return;
10494 }
10495
10496 int
10497 RegisterMove()
10498 {
10499     FILE *f;
10500     char string[MSG_SIZ];
10501
10502     if (   cmailMailedMove
10503         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10504         return TRUE;            /* Allow free viewing  */
10505     }
10506
10507     /* Unregister move to ensure that we don't leave RegisterMove        */
10508     /* with the move registered when the conditions for registering no   */
10509     /* longer hold                                                       */
10510     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10511         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10512         nCmailMovesRegistered --;
10513
10514         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10515           {
10516               free(cmailCommentList[lastLoadGameNumber - 1]);
10517               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10518           }
10519     }
10520
10521     if (cmailOldMove == -1) {
10522         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10523         return FALSE;
10524     }
10525
10526     if (currentMove > cmailOldMove + 1) {
10527         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10528         return FALSE;
10529     }
10530
10531     if (currentMove < cmailOldMove) {
10532         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10533         return FALSE;
10534     }
10535
10536     if (forwardMostMove > currentMove) {
10537         /* Silently truncate extra moves */
10538         TruncateGame();
10539     }
10540
10541     if (   (currentMove == cmailOldMove + 1)
10542         || (   (currentMove == cmailOldMove)
10543             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10544                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10545         if (gameInfo.result != GameUnfinished) {
10546             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10547         }
10548
10549         if (commentList[currentMove] != NULL) {
10550             cmailCommentList[lastLoadGameNumber - 1]
10551               = StrSave(commentList[currentMove]);
10552         }
10553         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10554
10555         if (appData.debugMode)
10556           fprintf(debugFP, "Saving %s for game %d\n",
10557                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10558
10559         sprintf(string,
10560                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10561         
10562         f = fopen(string, "w");
10563         if (appData.oldSaveStyle) {
10564             SaveGameOldStyle(f); /* also closes the file */
10565             
10566             sprintf(string, "%s.pos.out", appData.cmailGameName);
10567             f = fopen(string, "w");
10568             SavePosition(f, 0, NULL); /* also closes the file */
10569         } else {
10570             fprintf(f, "{--------------\n");
10571             PrintPosition(f, currentMove);
10572             fprintf(f, "--------------}\n\n");
10573             
10574             SaveGame(f, 0, NULL); /* also closes the file*/
10575         }
10576         
10577         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10578         nCmailMovesRegistered ++;
10579     } else if (nCmailGames == 1) {
10580         DisplayError(_("You have not made a move yet"), 0);
10581         return FALSE;
10582     }
10583
10584     return TRUE;
10585 }
10586
10587 void
10588 MailMoveEvent()
10589 {
10590 #if !WIN32
10591     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10592     FILE *commandOutput;
10593     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10594     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10595     int nBuffers;
10596     int i;
10597     int archived;
10598     char *arcDir;
10599
10600     if (! cmailMsgLoaded) {
10601         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10602         return;
10603     }
10604
10605     if (nCmailGames == nCmailResults) {
10606         DisplayError(_("No unfinished games"), 0);
10607         return;
10608     }
10609
10610 #if CMAIL_PROHIBIT_REMAIL
10611     if (cmailMailedMove) {
10612         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);
10613         DisplayError(msg, 0);
10614         return;
10615     }
10616 #endif
10617
10618     if (! (cmailMailedMove || RegisterMove())) return;
10619     
10620     if (   cmailMailedMove
10621         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10622         sprintf(string, partCommandString,
10623                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10624         commandOutput = popen(string, "r");
10625
10626         if (commandOutput == NULL) {
10627             DisplayError(_("Failed to invoke cmail"), 0);
10628         } else {
10629             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10630                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10631             }
10632             if (nBuffers > 1) {
10633                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10634                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10635                 nBytes = MSG_SIZ - 1;
10636             } else {
10637                 (void) memcpy(msg, buffer, nBytes);
10638             }
10639             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10640
10641             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10642                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10643
10644                 archived = TRUE;
10645                 for (i = 0; i < nCmailGames; i ++) {
10646                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10647                         archived = FALSE;
10648                     }
10649                 }
10650                 if (   archived
10651                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10652                         != NULL)) {
10653                     sprintf(buffer, "%s/%s.%s.archive",
10654                             arcDir,
10655                             appData.cmailGameName,
10656                             gameInfo.date);
10657                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10658                     cmailMsgLoaded = FALSE;
10659                 }
10660             }
10661
10662             DisplayInformation(msg);
10663             pclose(commandOutput);
10664         }
10665     } else {
10666         if ((*cmailMsg) != '\0') {
10667             DisplayInformation(cmailMsg);
10668         }
10669     }
10670
10671     return;
10672 #endif /* !WIN32 */
10673 }
10674
10675 char *
10676 CmailMsg()
10677 {
10678 #if WIN32
10679     return NULL;
10680 #else
10681     int  prependComma = 0;
10682     char number[5];
10683     char string[MSG_SIZ];       /* Space for game-list */
10684     int  i;
10685     
10686     if (!cmailMsgLoaded) return "";
10687
10688     if (cmailMailedMove) {
10689         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10690     } else {
10691         /* Create a list of games left */
10692         sprintf(string, "[");
10693         for (i = 0; i < nCmailGames; i ++) {
10694             if (! (   cmailMoveRegistered[i]
10695                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10696                 if (prependComma) {
10697                     sprintf(number, ",%d", i + 1);
10698                 } else {
10699                     sprintf(number, "%d", i + 1);
10700                     prependComma = 1;
10701                 }
10702                 
10703                 strcat(string, number);
10704             }
10705         }
10706         strcat(string, "]");
10707
10708         if (nCmailMovesRegistered + nCmailResults == 0) {
10709             switch (nCmailGames) {
10710               case 1:
10711                 sprintf(cmailMsg,
10712                         _("Still need to make move for game\n"));
10713                 break;
10714                 
10715               case 2:
10716                 sprintf(cmailMsg,
10717                         _("Still need to make moves for both games\n"));
10718                 break;
10719                 
10720               default:
10721                 sprintf(cmailMsg,
10722                         _("Still need to make moves for all %d games\n"),
10723                         nCmailGames);
10724                 break;
10725             }
10726         } else {
10727             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10728               case 1:
10729                 sprintf(cmailMsg,
10730                         _("Still need to make a move for game %s\n"),
10731                         string);
10732                 break;
10733                 
10734               case 0:
10735                 if (nCmailResults == nCmailGames) {
10736                     sprintf(cmailMsg, _("No unfinished games\n"));
10737                 } else {
10738                     sprintf(cmailMsg, _("Ready to send mail\n"));
10739                 }
10740                 break;
10741                 
10742               default:
10743                 sprintf(cmailMsg,
10744                         _("Still need to make moves for games %s\n"),
10745                         string);
10746             }
10747         }
10748     }
10749     return cmailMsg;
10750 #endif /* WIN32 */
10751 }
10752
10753 void
10754 ResetGameEvent()
10755 {
10756     if (gameMode == Training)
10757       SetTrainingModeOff();
10758
10759     Reset(TRUE, TRUE);
10760     cmailMsgLoaded = FALSE;
10761     if (appData.icsActive) {
10762       SendToICS(ics_prefix);
10763       SendToICS("refresh\n");
10764     }
10765 }
10766
10767 void
10768 ExitEvent(status)
10769      int status;
10770 {
10771     exiting++;
10772     if (exiting > 2) {
10773       /* Give up on clean exit */
10774       exit(status);
10775     }
10776     if (exiting > 1) {
10777       /* Keep trying for clean exit */
10778       return;
10779     }
10780
10781     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10782
10783     if (telnetISR != NULL) {
10784       RemoveInputSource(telnetISR);
10785     }
10786     if (icsPR != NoProc) {
10787       DestroyChildProcess(icsPR, TRUE);
10788     }
10789
10790     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10791     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10792
10793     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10794     /* make sure this other one finishes before killing it!                  */
10795     if(endingGame) { int count = 0;
10796         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10797         while(endingGame && count++ < 10) DoSleep(1);
10798         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10799     }
10800
10801     /* Kill off chess programs */
10802     if (first.pr != NoProc) {
10803         ExitAnalyzeMode();
10804         
10805         DoSleep( appData.delayBeforeQuit );
10806         SendToProgram("quit\n", &first);
10807         DoSleep( appData.delayAfterQuit );
10808         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10809     }
10810     if (second.pr != NoProc) {
10811         DoSleep( appData.delayBeforeQuit );
10812         SendToProgram("quit\n", &second);
10813         DoSleep( appData.delayAfterQuit );
10814         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10815     }
10816     if (first.isr != NULL) {
10817         RemoveInputSource(first.isr);
10818     }
10819     if (second.isr != NULL) {
10820         RemoveInputSource(second.isr);
10821     }
10822
10823     ShutDownFrontEnd();
10824     exit(status);
10825 }
10826
10827 void
10828 PauseEvent()
10829 {
10830     if (appData.debugMode)
10831         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10832     if (pausing) {
10833         pausing = FALSE;
10834         ModeHighlight();
10835         if (gameMode == MachinePlaysWhite ||
10836             gameMode == MachinePlaysBlack) {
10837             StartClocks();
10838         } else {
10839             DisplayBothClocks();
10840         }
10841         if (gameMode == PlayFromGameFile) {
10842             if (appData.timeDelay >= 0) 
10843                 AutoPlayGameLoop();
10844         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10845             Reset(FALSE, TRUE);
10846             SendToICS(ics_prefix);
10847             SendToICS("refresh\n");
10848         } else if (currentMove < forwardMostMove) {
10849             ForwardInner(forwardMostMove);
10850         }
10851         pauseExamInvalid = FALSE;
10852     } else {
10853         switch (gameMode) {
10854           default:
10855             return;
10856           case IcsExamining:
10857             pauseExamForwardMostMove = forwardMostMove;
10858             pauseExamInvalid = FALSE;
10859             /* fall through */
10860           case IcsObserving:
10861           case IcsPlayingWhite:
10862           case IcsPlayingBlack:
10863             pausing = TRUE;
10864             ModeHighlight();
10865             return;
10866           case PlayFromGameFile:
10867             (void) StopLoadGameTimer();
10868             pausing = TRUE;
10869             ModeHighlight();
10870             break;
10871           case BeginningOfGame:
10872             if (appData.icsActive) return;
10873             /* else fall through */
10874           case MachinePlaysWhite:
10875           case MachinePlaysBlack:
10876           case TwoMachinesPlay:
10877             if (forwardMostMove == 0)
10878               return;           /* don't pause if no one has moved */
10879             if ((gameMode == MachinePlaysWhite &&
10880                  !WhiteOnMove(forwardMostMove)) ||
10881                 (gameMode == MachinePlaysBlack &&
10882                  WhiteOnMove(forwardMostMove))) {
10883                 StopClocks();
10884             }
10885             pausing = TRUE;
10886             ModeHighlight();
10887             break;
10888         }
10889     }
10890 }
10891
10892 void
10893 EditCommentEvent()
10894 {
10895     char title[MSG_SIZ];
10896
10897     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10898         strcpy(title, _("Edit comment"));
10899     } else {
10900         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10901                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10902                 parseList[currentMove - 1]);
10903     }
10904
10905     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10906 }
10907
10908
10909 void
10910 EditTagsEvent()
10911 {
10912     char *tags = PGNTags(&gameInfo);
10913     EditTagsPopUp(tags);
10914     free(tags);
10915 }
10916
10917 void
10918 AnalyzeModeEvent()
10919 {
10920     if (appData.noChessProgram || gameMode == AnalyzeMode)
10921       return;
10922
10923     if (gameMode != AnalyzeFile) {
10924         if (!appData.icsEngineAnalyze) {
10925                EditGameEvent();
10926                if (gameMode != EditGame) return;
10927         }
10928         ResurrectChessProgram();
10929         SendToProgram("analyze\n", &first);
10930         first.analyzing = TRUE;
10931         /*first.maybeThinking = TRUE;*/
10932         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10933         EngineOutputPopUp();
10934     }
10935     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10936     pausing = FALSE;
10937     ModeHighlight();
10938     SetGameInfo();
10939
10940     StartAnalysisClock();
10941     GetTimeMark(&lastNodeCountTime);
10942     lastNodeCount = 0;
10943 }
10944
10945 void
10946 AnalyzeFileEvent()
10947 {
10948     if (appData.noChessProgram || gameMode == AnalyzeFile)
10949       return;
10950
10951     if (gameMode != AnalyzeMode) {
10952         EditGameEvent();
10953         if (gameMode != EditGame) return;
10954         ResurrectChessProgram();
10955         SendToProgram("analyze\n", &first);
10956         first.analyzing = TRUE;
10957         /*first.maybeThinking = TRUE;*/
10958         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10959         EngineOutputPopUp();
10960     }
10961     gameMode = AnalyzeFile;
10962     pausing = FALSE;
10963     ModeHighlight();
10964     SetGameInfo();
10965
10966     StartAnalysisClock();
10967     GetTimeMark(&lastNodeCountTime);
10968     lastNodeCount = 0;
10969 }
10970
10971 void
10972 MachineWhiteEvent()
10973 {
10974     char buf[MSG_SIZ];
10975     char *bookHit = NULL;
10976
10977     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10978       return;
10979
10980
10981     if (gameMode == PlayFromGameFile || 
10982         gameMode == TwoMachinesPlay  || 
10983         gameMode == Training         || 
10984         gameMode == AnalyzeMode      || 
10985         gameMode == EndOfGame)
10986         EditGameEvent();
10987
10988     if (gameMode == EditPosition) 
10989         EditPositionDone(TRUE);
10990
10991     if (!WhiteOnMove(currentMove)) {
10992         DisplayError(_("It is not White's turn"), 0);
10993         return;
10994     }
10995   
10996     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10997       ExitAnalyzeMode();
10998
10999     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11000         gameMode == AnalyzeFile)
11001         TruncateGame();
11002
11003     ResurrectChessProgram();    /* in case it isn't running */
11004     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11005         gameMode = MachinePlaysWhite;
11006         ResetClocks();
11007     } else
11008     gameMode = MachinePlaysWhite;
11009     pausing = FALSE;
11010     ModeHighlight();
11011     SetGameInfo();
11012     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11013     DisplayTitle(buf);
11014     if (first.sendName) {
11015       sprintf(buf, "name %s\n", gameInfo.black);
11016       SendToProgram(buf, &first);
11017     }
11018     if (first.sendTime) {
11019       if (first.useColors) {
11020         SendToProgram("black\n", &first); /*gnu kludge*/
11021       }
11022       SendTimeRemaining(&first, TRUE);
11023     }
11024     if (first.useColors) {
11025       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11026     }
11027     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11028     SetMachineThinkingEnables();
11029     first.maybeThinking = TRUE;
11030     StartClocks();
11031     firstMove = FALSE;
11032
11033     if (appData.autoFlipView && !flipView) {
11034       flipView = !flipView;
11035       DrawPosition(FALSE, NULL);
11036       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11037     }
11038
11039     if(bookHit) { // [HGM] book: simulate book reply
11040         static char bookMove[MSG_SIZ]; // a bit generous?
11041
11042         programStats.nodes = programStats.depth = programStats.time = 
11043         programStats.score = programStats.got_only_move = 0;
11044         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11045
11046         strcpy(bookMove, "move ");
11047         strcat(bookMove, bookHit);
11048         HandleMachineMove(bookMove, &first);
11049     }
11050 }
11051
11052 void
11053 MachineBlackEvent()
11054 {
11055     char buf[MSG_SIZ];
11056    char *bookHit = NULL;
11057
11058     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11059         return;
11060
11061
11062     if (gameMode == PlayFromGameFile || 
11063         gameMode == TwoMachinesPlay  || 
11064         gameMode == Training         || 
11065         gameMode == AnalyzeMode      || 
11066         gameMode == EndOfGame)
11067         EditGameEvent();
11068
11069     if (gameMode == EditPosition) 
11070         EditPositionDone(TRUE);
11071
11072     if (WhiteOnMove(currentMove)) {
11073         DisplayError(_("It is not Black's turn"), 0);
11074         return;
11075     }
11076     
11077     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11078       ExitAnalyzeMode();
11079
11080     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11081         gameMode == AnalyzeFile)
11082         TruncateGame();
11083
11084     ResurrectChessProgram();    /* in case it isn't running */
11085     gameMode = MachinePlaysBlack;
11086     pausing = FALSE;
11087     ModeHighlight();
11088     SetGameInfo();
11089     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11090     DisplayTitle(buf);
11091     if (first.sendName) {
11092       sprintf(buf, "name %s\n", gameInfo.white);
11093       SendToProgram(buf, &first);
11094     }
11095     if (first.sendTime) {
11096       if (first.useColors) {
11097         SendToProgram("white\n", &first); /*gnu kludge*/
11098       }
11099       SendTimeRemaining(&first, FALSE);
11100     }
11101     if (first.useColors) {
11102       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11103     }
11104     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11105     SetMachineThinkingEnables();
11106     first.maybeThinking = TRUE;
11107     StartClocks();
11108
11109     if (appData.autoFlipView && flipView) {
11110       flipView = !flipView;
11111       DrawPosition(FALSE, NULL);
11112       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11113     }
11114     if(bookHit) { // [HGM] book: simulate book reply
11115         static char bookMove[MSG_SIZ]; // a bit generous?
11116
11117         programStats.nodes = programStats.depth = programStats.time = 
11118         programStats.score = programStats.got_only_move = 0;
11119         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11120
11121         strcpy(bookMove, "move ");
11122         strcat(bookMove, bookHit);
11123         HandleMachineMove(bookMove, &first);
11124     }
11125 }
11126
11127
11128 void
11129 DisplayTwoMachinesTitle()
11130 {
11131     char buf[MSG_SIZ];
11132     if (appData.matchGames > 0) {
11133         if (first.twoMachinesColor[0] == 'w') {
11134             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11135                     gameInfo.white, gameInfo.black,
11136                     first.matchWins, second.matchWins,
11137                     matchGame - 1 - (first.matchWins + second.matchWins));
11138         } else {
11139             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11140                     gameInfo.white, gameInfo.black,
11141                     second.matchWins, first.matchWins,
11142                     matchGame - 1 - (first.matchWins + second.matchWins));
11143         }
11144     } else {
11145         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11146     }
11147     DisplayTitle(buf);
11148 }
11149
11150 void
11151 TwoMachinesEvent P((void))
11152 {
11153     int i;
11154     char buf[MSG_SIZ];
11155     ChessProgramState *onmove;
11156     char *bookHit = NULL;
11157     
11158     if (appData.noChessProgram) return;
11159
11160     switch (gameMode) {
11161       case TwoMachinesPlay:
11162         return;
11163       case MachinePlaysWhite:
11164       case MachinePlaysBlack:
11165         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11166             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11167             return;
11168         }
11169         /* fall through */
11170       case BeginningOfGame:
11171       case PlayFromGameFile:
11172       case EndOfGame:
11173         EditGameEvent();
11174         if (gameMode != EditGame) return;
11175         break;
11176       case EditPosition:
11177         EditPositionDone(TRUE);
11178         break;
11179       case AnalyzeMode:
11180       case AnalyzeFile:
11181         ExitAnalyzeMode();
11182         break;
11183       case EditGame:
11184       default:
11185         break;
11186     }
11187
11188 //    forwardMostMove = currentMove;
11189     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11190     ResurrectChessProgram();    /* in case first program isn't running */
11191
11192     if (second.pr == NULL) {
11193         StartChessProgram(&second);
11194         if (second.protocolVersion == 1) {
11195           TwoMachinesEventIfReady();
11196         } else {
11197           /* kludge: allow timeout for initial "feature" command */
11198           FreezeUI();
11199           DisplayMessage("", _("Starting second chess program"));
11200           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11201         }
11202         return;
11203     }
11204     DisplayMessage("", "");
11205     InitChessProgram(&second, FALSE);
11206     SendToProgram("force\n", &second);
11207     if (startedFromSetupPosition) {
11208         SendBoard(&second, backwardMostMove);
11209     if (appData.debugMode) {
11210         fprintf(debugFP, "Two Machines\n");
11211     }
11212     }
11213     for (i = backwardMostMove; i < forwardMostMove; i++) {
11214         SendMoveToProgram(i, &second);
11215     }
11216
11217     gameMode = TwoMachinesPlay;
11218     pausing = FALSE;
11219     ModeHighlight();
11220     SetGameInfo();
11221     DisplayTwoMachinesTitle();
11222     firstMove = TRUE;
11223     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11224         onmove = &first;
11225     } else {
11226         onmove = &second;
11227     }
11228
11229     SendToProgram(first.computerString, &first);
11230     if (first.sendName) {
11231       sprintf(buf, "name %s\n", second.tidy);
11232       SendToProgram(buf, &first);
11233     }
11234     SendToProgram(second.computerString, &second);
11235     if (second.sendName) {
11236       sprintf(buf, "name %s\n", first.tidy);
11237       SendToProgram(buf, &second);
11238     }
11239
11240     ResetClocks();
11241     if (!first.sendTime || !second.sendTime) {
11242         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11243         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11244     }
11245     if (onmove->sendTime) {
11246       if (onmove->useColors) {
11247         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11248       }
11249       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11250     }
11251     if (onmove->useColors) {
11252       SendToProgram(onmove->twoMachinesColor, onmove);
11253     }
11254     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11255 //    SendToProgram("go\n", onmove);
11256     onmove->maybeThinking = TRUE;
11257     SetMachineThinkingEnables();
11258
11259     StartClocks();
11260
11261     if(bookHit) { // [HGM] book: simulate book reply
11262         static char bookMove[MSG_SIZ]; // a bit generous?
11263
11264         programStats.nodes = programStats.depth = programStats.time = 
11265         programStats.score = programStats.got_only_move = 0;
11266         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11267
11268         strcpy(bookMove, "move ");
11269         strcat(bookMove, bookHit);
11270         savedMessage = bookMove; // args for deferred call
11271         savedState = onmove;
11272         ScheduleDelayedEvent(DeferredBookMove, 1);
11273     }
11274 }
11275
11276 void
11277 TrainingEvent()
11278 {
11279     if (gameMode == Training) {
11280       SetTrainingModeOff();
11281       gameMode = PlayFromGameFile;
11282       DisplayMessage("", _("Training mode off"));
11283     } else {
11284       gameMode = Training;
11285       animateTraining = appData.animate;
11286
11287       /* make sure we are not already at the end of the game */
11288       if (currentMove < forwardMostMove) {
11289         SetTrainingModeOn();
11290         DisplayMessage("", _("Training mode on"));
11291       } else {
11292         gameMode = PlayFromGameFile;
11293         DisplayError(_("Already at end of game"), 0);
11294       }
11295     }
11296     ModeHighlight();
11297 }
11298
11299 void
11300 IcsClientEvent()
11301 {
11302     if (!appData.icsActive) return;
11303     switch (gameMode) {
11304       case IcsPlayingWhite:
11305       case IcsPlayingBlack:
11306       case IcsObserving:
11307       case IcsIdle:
11308       case BeginningOfGame:
11309       case IcsExamining:
11310         return;
11311
11312       case EditGame:
11313         break;
11314
11315       case EditPosition:
11316         EditPositionDone(TRUE);
11317         break;
11318
11319       case AnalyzeMode:
11320       case AnalyzeFile:
11321         ExitAnalyzeMode();
11322         break;
11323         
11324       default:
11325         EditGameEvent();
11326         break;
11327     }
11328
11329     gameMode = IcsIdle;
11330     ModeHighlight();
11331     return;
11332 }
11333
11334
11335 void
11336 EditGameEvent()
11337 {
11338     int i;
11339
11340     switch (gameMode) {
11341       case Training:
11342         SetTrainingModeOff();
11343         break;
11344       case MachinePlaysWhite:
11345       case MachinePlaysBlack:
11346       case BeginningOfGame:
11347         SendToProgram("force\n", &first);
11348         SetUserThinkingEnables();
11349         break;
11350       case PlayFromGameFile:
11351         (void) StopLoadGameTimer();
11352         if (gameFileFP != NULL) {
11353             gameFileFP = NULL;
11354         }
11355         break;
11356       case EditPosition:
11357         EditPositionDone(TRUE);
11358         break;
11359       case AnalyzeMode:
11360       case AnalyzeFile:
11361         ExitAnalyzeMode();
11362         SendToProgram("force\n", &first);
11363         break;
11364       case TwoMachinesPlay:
11365         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11366         ResurrectChessProgram();
11367         SetUserThinkingEnables();
11368         break;
11369       case EndOfGame:
11370         ResurrectChessProgram();
11371         break;
11372       case IcsPlayingBlack:
11373       case IcsPlayingWhite:
11374         DisplayError(_("Warning: You are still playing a game"), 0);
11375         break;
11376       case IcsObserving:
11377         DisplayError(_("Warning: You are still observing a game"), 0);
11378         break;
11379       case IcsExamining:
11380         DisplayError(_("Warning: You are still examining a game"), 0);
11381         break;
11382       case IcsIdle:
11383         break;
11384       case EditGame:
11385       default:
11386         return;
11387     }
11388     
11389     pausing = FALSE;
11390     StopClocks();
11391     first.offeredDraw = second.offeredDraw = 0;
11392
11393     if (gameMode == PlayFromGameFile) {
11394         whiteTimeRemaining = timeRemaining[0][currentMove];
11395         blackTimeRemaining = timeRemaining[1][currentMove];
11396         DisplayTitle("");
11397     }
11398
11399     if (gameMode == MachinePlaysWhite ||
11400         gameMode == MachinePlaysBlack ||
11401         gameMode == TwoMachinesPlay ||
11402         gameMode == EndOfGame) {
11403         i = forwardMostMove;
11404         while (i > currentMove) {
11405             SendToProgram("undo\n", &first);
11406             i--;
11407         }
11408         whiteTimeRemaining = timeRemaining[0][currentMove];
11409         blackTimeRemaining = timeRemaining[1][currentMove];
11410         DisplayBothClocks();
11411         if (whiteFlag || blackFlag) {
11412             whiteFlag = blackFlag = 0;
11413         }
11414         DisplayTitle("");
11415     }           
11416     
11417     gameMode = EditGame;
11418     ModeHighlight();
11419     SetGameInfo();
11420 }
11421
11422
11423 void
11424 EditPositionEvent()
11425 {
11426     if (gameMode == EditPosition) {
11427         EditGameEvent();
11428         return;
11429     }
11430     
11431     EditGameEvent();
11432     if (gameMode != EditGame) return;
11433     
11434     gameMode = EditPosition;
11435     ModeHighlight();
11436     SetGameInfo();
11437     if (currentMove > 0)
11438       CopyBoard(boards[0], boards[currentMove]);
11439     
11440     blackPlaysFirst = !WhiteOnMove(currentMove);
11441     ResetClocks();
11442     currentMove = forwardMostMove = backwardMostMove = 0;
11443     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11444     DisplayMove(-1);
11445 }
11446
11447 void
11448 ExitAnalyzeMode()
11449 {
11450     /* [DM] icsEngineAnalyze - possible call from other functions */
11451     if (appData.icsEngineAnalyze) {
11452         appData.icsEngineAnalyze = FALSE;
11453
11454         DisplayMessage("",_("Close ICS engine analyze..."));
11455     }
11456     if (first.analysisSupport && first.analyzing) {
11457       SendToProgram("exit\n", &first);
11458       first.analyzing = FALSE;
11459     }
11460     thinkOutput[0] = NULLCHAR;
11461 }
11462
11463 void
11464 EditPositionDone(Boolean fakeRights)
11465 {
11466     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11467
11468     startedFromSetupPosition = TRUE;
11469     InitChessProgram(&first, FALSE);
11470     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11471       boards[0][EP_STATUS] = EP_NONE;
11472       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11473     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11474         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11475         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11476       } else boards[0][CASTLING][2] = NoRights;
11477     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11478         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11479         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11480       } else boards[0][CASTLING][5] = NoRights;
11481     }
11482     SendToProgram("force\n", &first);
11483     if (blackPlaysFirst) {
11484         strcpy(moveList[0], "");
11485         strcpy(parseList[0], "");
11486         currentMove = forwardMostMove = backwardMostMove = 1;
11487         CopyBoard(boards[1], boards[0]);
11488     } else {
11489         currentMove = forwardMostMove = backwardMostMove = 0;
11490     }
11491     SendBoard(&first, forwardMostMove);
11492     if (appData.debugMode) {
11493         fprintf(debugFP, "EditPosDone\n");
11494     }
11495     DisplayTitle("");
11496     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11497     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11498     gameMode = EditGame;
11499     ModeHighlight();
11500     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11501     ClearHighlights(); /* [AS] */
11502 }
11503
11504 /* Pause for `ms' milliseconds */
11505 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11506 void
11507 TimeDelay(ms)
11508      long ms;
11509 {
11510     TimeMark m1, m2;
11511
11512     GetTimeMark(&m1);
11513     do {
11514         GetTimeMark(&m2);
11515     } while (SubtractTimeMarks(&m2, &m1) < ms);
11516 }
11517
11518 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11519 void
11520 SendMultiLineToICS(buf)
11521      char *buf;
11522 {
11523     char temp[MSG_SIZ+1], *p;
11524     int len;
11525
11526     len = strlen(buf);
11527     if (len > MSG_SIZ)
11528       len = MSG_SIZ;
11529   
11530     strncpy(temp, buf, len);
11531     temp[len] = 0;
11532
11533     p = temp;
11534     while (*p) {
11535         if (*p == '\n' || *p == '\r')
11536           *p = ' ';
11537         ++p;
11538     }
11539
11540     strcat(temp, "\n");
11541     SendToICS(temp);
11542     SendToPlayer(temp, strlen(temp));
11543 }
11544
11545 void
11546 SetWhiteToPlayEvent()
11547 {
11548     if (gameMode == EditPosition) {
11549         blackPlaysFirst = FALSE;
11550         DisplayBothClocks();    /* works because currentMove is 0 */
11551     } else if (gameMode == IcsExamining) {
11552         SendToICS(ics_prefix);
11553         SendToICS("tomove white\n");
11554     }
11555 }
11556
11557 void
11558 SetBlackToPlayEvent()
11559 {
11560     if (gameMode == EditPosition) {
11561         blackPlaysFirst = TRUE;
11562         currentMove = 1;        /* kludge */
11563         DisplayBothClocks();
11564         currentMove = 0;
11565     } else if (gameMode == IcsExamining) {
11566         SendToICS(ics_prefix);
11567         SendToICS("tomove black\n");
11568     }
11569 }
11570
11571 void
11572 EditPositionMenuEvent(selection, x, y)
11573      ChessSquare selection;
11574      int x, y;
11575 {
11576     char buf[MSG_SIZ];
11577     ChessSquare piece = boards[0][y][x];
11578
11579     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11580
11581     switch (selection) {
11582       case ClearBoard:
11583         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11584             SendToICS(ics_prefix);
11585             SendToICS("bsetup clear\n");
11586         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11587             SendToICS(ics_prefix);
11588             SendToICS("clearboard\n");
11589         } else {
11590             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11591                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11592                 for (y = 0; y < BOARD_HEIGHT; y++) {
11593                     if (gameMode == IcsExamining) {
11594                         if (boards[currentMove][y][x] != EmptySquare) {
11595                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11596                                     AAA + x, ONE + y);
11597                             SendToICS(buf);
11598                         }
11599                     } else {
11600                         boards[0][y][x] = p;
11601                     }
11602                 }
11603             }
11604         }
11605         if (gameMode == EditPosition) {
11606             DrawPosition(FALSE, boards[0]);
11607         }
11608         break;
11609
11610       case WhitePlay:
11611         SetWhiteToPlayEvent();
11612         break;
11613
11614       case BlackPlay:
11615         SetBlackToPlayEvent();
11616         break;
11617
11618       case EmptySquare:
11619         if (gameMode == IcsExamining) {
11620             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11621             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11622             SendToICS(buf);
11623         } else {
11624             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11625                 if(x == BOARD_LEFT-2) {
11626                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11627                     boards[0][y][1] = 0;
11628                 } else
11629                 if(x == BOARD_RGHT+1) {
11630                     if(y >= gameInfo.holdingsSize) break;
11631                     boards[0][y][BOARD_WIDTH-2] = 0;
11632                 } else break;
11633             }
11634             boards[0][y][x] = EmptySquare;
11635             DrawPosition(FALSE, boards[0]);
11636         }
11637         break;
11638
11639       case PromotePiece:
11640         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11641            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11642             selection = (ChessSquare) (PROMOTED piece);
11643         } else if(piece == EmptySquare) selection = WhiteSilver;
11644         else selection = (ChessSquare)((int)piece - 1);
11645         goto defaultlabel;
11646
11647       case DemotePiece:
11648         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11649            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11650             selection = (ChessSquare) (DEMOTED piece);
11651         } else if(piece == EmptySquare) selection = BlackSilver;
11652         else selection = (ChessSquare)((int)piece + 1);       
11653         goto defaultlabel;
11654
11655       case WhiteQueen:
11656       case BlackQueen:
11657         if(gameInfo.variant == VariantShatranj ||
11658            gameInfo.variant == VariantXiangqi  ||
11659            gameInfo.variant == VariantCourier  ||
11660            gameInfo.variant == VariantMakruk     )
11661             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11662         goto defaultlabel;
11663
11664       case WhiteKing:
11665       case BlackKing:
11666         if(gameInfo.variant == VariantXiangqi)
11667             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11668         if(gameInfo.variant == VariantKnightmate)
11669             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11670       default:
11671         defaultlabel:
11672         if (gameMode == IcsExamining) {
11673             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11674             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11675                     PieceToChar(selection), AAA + x, ONE + y);
11676             SendToICS(buf);
11677         } else {
11678             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11679                 int n;
11680                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11681                     n = PieceToNumber(selection - BlackPawn);
11682                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11683                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11684                     boards[0][BOARD_HEIGHT-1-n][1]++;
11685                 } else
11686                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11687                     n = PieceToNumber(selection);
11688                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11689                     boards[0][n][BOARD_WIDTH-1] = selection;
11690                     boards[0][n][BOARD_WIDTH-2]++;
11691                 }
11692             } else
11693             boards[0][y][x] = selection;
11694             DrawPosition(TRUE, boards[0]);
11695         }
11696         break;
11697     }
11698 }
11699
11700
11701 void
11702 DropMenuEvent(selection, x, y)
11703      ChessSquare selection;
11704      int x, y;
11705 {
11706     ChessMove moveType;
11707
11708     switch (gameMode) {
11709       case IcsPlayingWhite:
11710       case MachinePlaysBlack:
11711         if (!WhiteOnMove(currentMove)) {
11712             DisplayMoveError(_("It is Black's turn"));
11713             return;
11714         }
11715         moveType = WhiteDrop;
11716         break;
11717       case IcsPlayingBlack:
11718       case MachinePlaysWhite:
11719         if (WhiteOnMove(currentMove)) {
11720             DisplayMoveError(_("It is White's turn"));
11721             return;
11722         }
11723         moveType = BlackDrop;
11724         break;
11725       case EditGame:
11726         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11727         break;
11728       default:
11729         return;
11730     }
11731
11732     if (moveType == BlackDrop && selection < BlackPawn) {
11733       selection = (ChessSquare) ((int) selection
11734                                  + (int) BlackPawn - (int) WhitePawn);
11735     }
11736     if (boards[currentMove][y][x] != EmptySquare) {
11737         DisplayMoveError(_("That square is occupied"));
11738         return;
11739     }
11740
11741     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11742 }
11743
11744 void
11745 AcceptEvent()
11746 {
11747     /* Accept a pending offer of any kind from opponent */
11748     
11749     if (appData.icsActive) {
11750         SendToICS(ics_prefix);
11751         SendToICS("accept\n");
11752     } else if (cmailMsgLoaded) {
11753         if (currentMove == cmailOldMove &&
11754             commentList[cmailOldMove] != NULL &&
11755             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11756                    "Black offers a draw" : "White offers a draw")) {
11757             TruncateGame();
11758             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11759             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11760         } else {
11761             DisplayError(_("There is no pending offer on this move"), 0);
11762             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11763         }
11764     } else {
11765         /* Not used for offers from chess program */
11766     }
11767 }
11768
11769 void
11770 DeclineEvent()
11771 {
11772     /* Decline a pending offer of any kind from opponent */
11773     
11774     if (appData.icsActive) {
11775         SendToICS(ics_prefix);
11776         SendToICS("decline\n");
11777     } else if (cmailMsgLoaded) {
11778         if (currentMove == cmailOldMove &&
11779             commentList[cmailOldMove] != NULL &&
11780             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11781                    "Black offers a draw" : "White offers a draw")) {
11782 #ifdef NOTDEF
11783             AppendComment(cmailOldMove, "Draw declined", TRUE);
11784             DisplayComment(cmailOldMove - 1, "Draw declined");
11785 #endif /*NOTDEF*/
11786         } else {
11787             DisplayError(_("There is no pending offer on this move"), 0);
11788         }
11789     } else {
11790         /* Not used for offers from chess program */
11791     }
11792 }
11793
11794 void
11795 RematchEvent()
11796 {
11797     /* Issue ICS rematch command */
11798     if (appData.icsActive) {
11799         SendToICS(ics_prefix);
11800         SendToICS("rematch\n");
11801     }
11802 }
11803
11804 void
11805 CallFlagEvent()
11806 {
11807     /* Call your opponent's flag (claim a win on time) */
11808     if (appData.icsActive) {
11809         SendToICS(ics_prefix);
11810         SendToICS("flag\n");
11811     } else {
11812         switch (gameMode) {
11813           default:
11814             return;
11815           case MachinePlaysWhite:
11816             if (whiteFlag) {
11817                 if (blackFlag)
11818                   GameEnds(GameIsDrawn, "Both players ran out of time",
11819                            GE_PLAYER);
11820                 else
11821                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11822             } else {
11823                 DisplayError(_("Your opponent is not out of time"), 0);
11824             }
11825             break;
11826           case MachinePlaysBlack:
11827             if (blackFlag) {
11828                 if (whiteFlag)
11829                   GameEnds(GameIsDrawn, "Both players ran out of time",
11830                            GE_PLAYER);
11831                 else
11832                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11833             } else {
11834                 DisplayError(_("Your opponent is not out of time"), 0);
11835             }
11836             break;
11837         }
11838     }
11839 }
11840
11841 void
11842 DrawEvent()
11843 {
11844     /* Offer draw or accept pending draw offer from opponent */
11845     
11846     if (appData.icsActive) {
11847         /* Note: tournament rules require draw offers to be
11848            made after you make your move but before you punch
11849            your clock.  Currently ICS doesn't let you do that;
11850            instead, you immediately punch your clock after making
11851            a move, but you can offer a draw at any time. */
11852         
11853         SendToICS(ics_prefix);
11854         SendToICS("draw\n");
11855         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
11856     } else if (cmailMsgLoaded) {
11857         if (currentMove == cmailOldMove &&
11858             commentList[cmailOldMove] != NULL &&
11859             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11860                    "Black offers a draw" : "White offers a draw")) {
11861             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11862             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11863         } else if (currentMove == cmailOldMove + 1) {
11864             char *offer = WhiteOnMove(cmailOldMove) ?
11865               "White offers a draw" : "Black offers a draw";
11866             AppendComment(currentMove, offer, TRUE);
11867             DisplayComment(currentMove - 1, offer);
11868             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11869         } else {
11870             DisplayError(_("You must make your move before offering a draw"), 0);
11871             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11872         }
11873     } else if (first.offeredDraw) {
11874         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11875     } else {
11876         if (first.sendDrawOffers) {
11877             SendToProgram("draw\n", &first);
11878             userOfferedDraw = TRUE;
11879         }
11880     }
11881 }
11882
11883 void
11884 AdjournEvent()
11885 {
11886     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11887     
11888     if (appData.icsActive) {
11889         SendToICS(ics_prefix);
11890         SendToICS("adjourn\n");
11891     } else {
11892         /* Currently GNU Chess doesn't offer or accept Adjourns */
11893     }
11894 }
11895
11896
11897 void
11898 AbortEvent()
11899 {
11900     /* Offer Abort or accept pending Abort offer from opponent */
11901     
11902     if (appData.icsActive) {
11903         SendToICS(ics_prefix);
11904         SendToICS("abort\n");
11905     } else {
11906         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11907     }
11908 }
11909
11910 void
11911 ResignEvent()
11912 {
11913     /* Resign.  You can do this even if it's not your turn. */
11914     
11915     if (appData.icsActive) {
11916         SendToICS(ics_prefix);
11917         SendToICS("resign\n");
11918     } else {
11919         switch (gameMode) {
11920           case MachinePlaysWhite:
11921             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11922             break;
11923           case MachinePlaysBlack:
11924             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11925             break;
11926           case EditGame:
11927             if (cmailMsgLoaded) {
11928                 TruncateGame();
11929                 if (WhiteOnMove(cmailOldMove)) {
11930                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11931                 } else {
11932                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11933                 }
11934                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11935             }
11936             break;
11937           default:
11938             break;
11939         }
11940     }
11941 }
11942
11943
11944 void
11945 StopObservingEvent()
11946 {
11947     /* Stop observing current games */
11948     SendToICS(ics_prefix);
11949     SendToICS("unobserve\n");
11950 }
11951
11952 void
11953 StopExaminingEvent()
11954 {
11955     /* Stop observing current game */
11956     SendToICS(ics_prefix);
11957     SendToICS("unexamine\n");
11958 }
11959
11960 void
11961 ForwardInner(target)
11962      int target;
11963 {
11964     int limit;
11965
11966     if (appData.debugMode)
11967         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11968                 target, currentMove, forwardMostMove);
11969
11970     if (gameMode == EditPosition)
11971       return;
11972
11973     if (gameMode == PlayFromGameFile && !pausing)
11974       PauseEvent();
11975     
11976     if (gameMode == IcsExamining && pausing)
11977       limit = pauseExamForwardMostMove;
11978     else
11979       limit = forwardMostMove;
11980     
11981     if (target > limit) target = limit;
11982
11983     if (target > 0 && moveList[target - 1][0]) {
11984         int fromX, fromY, toX, toY;
11985         toX = moveList[target - 1][2] - AAA;
11986         toY = moveList[target - 1][3] - ONE;
11987         if (moveList[target - 1][1] == '@') {
11988             if (appData.highlightLastMove) {
11989                 SetHighlights(-1, -1, toX, toY);
11990             }
11991         } else {
11992             fromX = moveList[target - 1][0] - AAA;
11993             fromY = moveList[target - 1][1] - ONE;
11994             if (target == currentMove + 1) {
11995                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11996             }
11997             if (appData.highlightLastMove) {
11998                 SetHighlights(fromX, fromY, toX, toY);
11999             }
12000         }
12001     }
12002     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12003         gameMode == Training || gameMode == PlayFromGameFile || 
12004         gameMode == AnalyzeFile) {
12005         while (currentMove < target) {
12006             SendMoveToProgram(currentMove++, &first);
12007         }
12008     } else {
12009         currentMove = target;
12010     }
12011     
12012     if (gameMode == EditGame || gameMode == EndOfGame) {
12013         whiteTimeRemaining = timeRemaining[0][currentMove];
12014         blackTimeRemaining = timeRemaining[1][currentMove];
12015     }
12016     DisplayBothClocks();
12017     DisplayMove(currentMove - 1);
12018     DrawPosition(FALSE, boards[currentMove]);
12019     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12020     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12021         DisplayComment(currentMove - 1, commentList[currentMove]);
12022     }
12023 }
12024
12025
12026 void
12027 ForwardEvent()
12028 {
12029     if (gameMode == IcsExamining && !pausing) {
12030         SendToICS(ics_prefix);
12031         SendToICS("forward\n");
12032     } else {
12033         ForwardInner(currentMove + 1);
12034     }
12035 }
12036
12037 void
12038 ToEndEvent()
12039 {
12040     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12041         /* to optimze, we temporarily turn off analysis mode while we feed
12042          * the remaining moves to the engine. Otherwise we get analysis output
12043          * after each move.
12044          */ 
12045         if (first.analysisSupport) {
12046           SendToProgram("exit\nforce\n", &first);
12047           first.analyzing = FALSE;
12048         }
12049     }
12050         
12051     if (gameMode == IcsExamining && !pausing) {
12052         SendToICS(ics_prefix);
12053         SendToICS("forward 999999\n");
12054     } else {
12055         ForwardInner(forwardMostMove);
12056     }
12057
12058     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12059         /* we have fed all the moves, so reactivate analysis mode */
12060         SendToProgram("analyze\n", &first);
12061         first.analyzing = TRUE;
12062         /*first.maybeThinking = TRUE;*/
12063         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12064     }
12065 }
12066
12067 void
12068 BackwardInner(target)
12069      int target;
12070 {
12071     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12072
12073     if (appData.debugMode)
12074         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12075                 target, currentMove, forwardMostMove);
12076
12077     if (gameMode == EditPosition) return;
12078     if (currentMove <= backwardMostMove) {
12079         ClearHighlights();
12080         DrawPosition(full_redraw, boards[currentMove]);
12081         return;
12082     }
12083     if (gameMode == PlayFromGameFile && !pausing)
12084       PauseEvent();
12085     
12086     if (moveList[target][0]) {
12087         int fromX, fromY, toX, toY;
12088         toX = moveList[target][2] - AAA;
12089         toY = moveList[target][3] - ONE;
12090         if (moveList[target][1] == '@') {
12091             if (appData.highlightLastMove) {
12092                 SetHighlights(-1, -1, toX, toY);
12093             }
12094         } else {
12095             fromX = moveList[target][0] - AAA;
12096             fromY = moveList[target][1] - ONE;
12097             if (target == currentMove - 1) {
12098                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12099             }
12100             if (appData.highlightLastMove) {
12101                 SetHighlights(fromX, fromY, toX, toY);
12102             }
12103         }
12104     }
12105     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12106         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12107         while (currentMove > target) {
12108             SendToProgram("undo\n", &first);
12109             currentMove--;
12110         }
12111     } else {
12112         currentMove = target;
12113     }
12114     
12115     if (gameMode == EditGame || gameMode == EndOfGame) {
12116         whiteTimeRemaining = timeRemaining[0][currentMove];
12117         blackTimeRemaining = timeRemaining[1][currentMove];
12118     }
12119     DisplayBothClocks();
12120     DisplayMove(currentMove - 1);
12121     DrawPosition(full_redraw, boards[currentMove]);
12122     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12123     // [HGM] PV info: routine tests if comment empty
12124     DisplayComment(currentMove - 1, commentList[currentMove]);
12125 }
12126
12127 void
12128 BackwardEvent()
12129 {
12130     if (gameMode == IcsExamining && !pausing) {
12131         SendToICS(ics_prefix);
12132         SendToICS("backward\n");
12133     } else {
12134         BackwardInner(currentMove - 1);
12135     }
12136 }
12137
12138 void
12139 ToStartEvent()
12140 {
12141     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12142         /* to optimize, we temporarily turn off analysis mode while we undo
12143          * all the moves. Otherwise we get analysis output after each undo.
12144          */ 
12145         if (first.analysisSupport) {
12146           SendToProgram("exit\nforce\n", &first);
12147           first.analyzing = FALSE;
12148         }
12149     }
12150
12151     if (gameMode == IcsExamining && !pausing) {
12152         SendToICS(ics_prefix);
12153         SendToICS("backward 999999\n");
12154     } else {
12155         BackwardInner(backwardMostMove);
12156     }
12157
12158     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12159         /* we have fed all the moves, so reactivate analysis mode */
12160         SendToProgram("analyze\n", &first);
12161         first.analyzing = TRUE;
12162         /*first.maybeThinking = TRUE;*/
12163         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12164     }
12165 }
12166
12167 void
12168 ToNrEvent(int to)
12169 {
12170   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12171   if (to >= forwardMostMove) to = forwardMostMove;
12172   if (to <= backwardMostMove) to = backwardMostMove;
12173   if (to < currentMove) {
12174     BackwardInner(to);
12175   } else {
12176     ForwardInner(to);
12177   }
12178 }
12179
12180 void
12181 RevertEvent()
12182 {
12183     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12184         return;
12185     }
12186     if (gameMode != IcsExamining) {
12187         DisplayError(_("You are not examining a game"), 0);
12188         return;
12189     }
12190     if (pausing) {
12191         DisplayError(_("You can't revert while pausing"), 0);
12192         return;
12193     }
12194     SendToICS(ics_prefix);
12195     SendToICS("revert\n");
12196 }
12197
12198 void
12199 RetractMoveEvent()
12200 {
12201     switch (gameMode) {
12202       case MachinePlaysWhite:
12203       case MachinePlaysBlack:
12204         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12205             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12206             return;
12207         }
12208         if (forwardMostMove < 2) return;
12209         currentMove = forwardMostMove = forwardMostMove - 2;
12210         whiteTimeRemaining = timeRemaining[0][currentMove];
12211         blackTimeRemaining = timeRemaining[1][currentMove];
12212         DisplayBothClocks();
12213         DisplayMove(currentMove - 1);
12214         ClearHighlights();/*!! could figure this out*/
12215         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12216         SendToProgram("remove\n", &first);
12217         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12218         break;
12219
12220       case BeginningOfGame:
12221       default:
12222         break;
12223
12224       case IcsPlayingWhite:
12225       case IcsPlayingBlack:
12226         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12227             SendToICS(ics_prefix);
12228             SendToICS("takeback 2\n");
12229         } else {
12230             SendToICS(ics_prefix);
12231             SendToICS("takeback 1\n");
12232         }
12233         break;
12234     }
12235 }
12236
12237 void
12238 MoveNowEvent()
12239 {
12240     ChessProgramState *cps;
12241
12242     switch (gameMode) {
12243       case MachinePlaysWhite:
12244         if (!WhiteOnMove(forwardMostMove)) {
12245             DisplayError(_("It is your turn"), 0);
12246             return;
12247         }
12248         cps = &first;
12249         break;
12250       case MachinePlaysBlack:
12251         if (WhiteOnMove(forwardMostMove)) {
12252             DisplayError(_("It is your turn"), 0);
12253             return;
12254         }
12255         cps = &first;
12256         break;
12257       case TwoMachinesPlay:
12258         if (WhiteOnMove(forwardMostMove) ==
12259             (first.twoMachinesColor[0] == 'w')) {
12260             cps = &first;
12261         } else {
12262             cps = &second;
12263         }
12264         break;
12265       case BeginningOfGame:
12266       default:
12267         return;
12268     }
12269     SendToProgram("?\n", cps);
12270 }
12271
12272 void
12273 TruncateGameEvent()
12274 {
12275     EditGameEvent();
12276     if (gameMode != EditGame) return;
12277     TruncateGame();
12278 }
12279
12280 void
12281 TruncateGame()
12282 {
12283     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12284     if (forwardMostMove > currentMove) {
12285         if (gameInfo.resultDetails != NULL) {
12286             free(gameInfo.resultDetails);
12287             gameInfo.resultDetails = NULL;
12288             gameInfo.result = GameUnfinished;
12289         }
12290         forwardMostMove = currentMove;
12291         HistorySet(parseList, backwardMostMove, forwardMostMove,
12292                    currentMove-1);
12293     }
12294 }
12295
12296 void
12297 HintEvent()
12298 {
12299     if (appData.noChessProgram) return;
12300     switch (gameMode) {
12301       case MachinePlaysWhite:
12302         if (WhiteOnMove(forwardMostMove)) {
12303             DisplayError(_("Wait until your turn"), 0);
12304             return;
12305         }
12306         break;
12307       case BeginningOfGame:
12308       case MachinePlaysBlack:
12309         if (!WhiteOnMove(forwardMostMove)) {
12310             DisplayError(_("Wait until your turn"), 0);
12311             return;
12312         }
12313         break;
12314       default:
12315         DisplayError(_("No hint available"), 0);
12316         return;
12317     }
12318     SendToProgram("hint\n", &first);
12319     hintRequested = TRUE;
12320 }
12321
12322 void
12323 BookEvent()
12324 {
12325     if (appData.noChessProgram) return;
12326     switch (gameMode) {
12327       case MachinePlaysWhite:
12328         if (WhiteOnMove(forwardMostMove)) {
12329             DisplayError(_("Wait until your turn"), 0);
12330             return;
12331         }
12332         break;
12333       case BeginningOfGame:
12334       case MachinePlaysBlack:
12335         if (!WhiteOnMove(forwardMostMove)) {
12336             DisplayError(_("Wait until your turn"), 0);
12337             return;
12338         }
12339         break;
12340       case EditPosition:
12341         EditPositionDone(TRUE);
12342         break;
12343       case TwoMachinesPlay:
12344         return;
12345       default:
12346         break;
12347     }
12348     SendToProgram("bk\n", &first);
12349     bookOutput[0] = NULLCHAR;
12350     bookRequested = TRUE;
12351 }
12352
12353 void
12354 AboutGameEvent()
12355 {
12356     char *tags = PGNTags(&gameInfo);
12357     TagsPopUp(tags, CmailMsg());
12358     free(tags);
12359 }
12360
12361 /* end button procedures */
12362
12363 void
12364 PrintPosition(fp, move)
12365      FILE *fp;
12366      int move;
12367 {
12368     int i, j;
12369     
12370     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12371         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12372             char c = PieceToChar(boards[move][i][j]);
12373             fputc(c == 'x' ? '.' : c, fp);
12374             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12375         }
12376     }
12377     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12378       fprintf(fp, "white to play\n");
12379     else
12380       fprintf(fp, "black to play\n");
12381 }
12382
12383 void
12384 PrintOpponents(fp)
12385      FILE *fp;
12386 {
12387     if (gameInfo.white != NULL) {
12388         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12389     } else {
12390         fprintf(fp, "\n");
12391     }
12392 }
12393
12394 /* Find last component of program's own name, using some heuristics */
12395 void
12396 TidyProgramName(prog, host, buf)
12397      char *prog, *host, buf[MSG_SIZ];
12398 {
12399     char *p, *q;
12400     int local = (strcmp(host, "localhost") == 0);
12401     while (!local && (p = strchr(prog, ';')) != NULL) {
12402         p++;
12403         while (*p == ' ') p++;
12404         prog = p;
12405     }
12406     if (*prog == '"' || *prog == '\'') {
12407         q = strchr(prog + 1, *prog);
12408     } else {
12409         q = strchr(prog, ' ');
12410     }
12411     if (q == NULL) q = prog + strlen(prog);
12412     p = q;
12413     while (p >= prog && *p != '/' && *p != '\\') p--;
12414     p++;
12415     if(p == prog && *p == '"') p++;
12416     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12417     memcpy(buf, p, q - p);
12418     buf[q - p] = NULLCHAR;
12419     if (!local) {
12420         strcat(buf, "@");
12421         strcat(buf, host);
12422     }
12423 }
12424
12425 char *
12426 TimeControlTagValue()
12427 {
12428     char buf[MSG_SIZ];
12429     if (!appData.clockMode) {
12430         strcpy(buf, "-");
12431     } else if (movesPerSession > 0) {
12432         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12433     } else if (timeIncrement == 0) {
12434         sprintf(buf, "%ld", timeControl/1000);
12435     } else {
12436         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12437     }
12438     return StrSave(buf);
12439 }
12440
12441 void
12442 SetGameInfo()
12443 {
12444     /* This routine is used only for certain modes */
12445     VariantClass v = gameInfo.variant;
12446     ChessMove r = GameUnfinished;
12447     char *p = NULL;
12448
12449     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12450         r = gameInfo.result; 
12451         p = gameInfo.resultDetails; 
12452         gameInfo.resultDetails = NULL;
12453     }
12454     ClearGameInfo(&gameInfo);
12455     gameInfo.variant = v;
12456
12457     switch (gameMode) {
12458       case MachinePlaysWhite:
12459         gameInfo.event = StrSave( appData.pgnEventHeader );
12460         gameInfo.site = StrSave(HostName());
12461         gameInfo.date = PGNDate();
12462         gameInfo.round = StrSave("-");
12463         gameInfo.white = StrSave(first.tidy);
12464         gameInfo.black = StrSave(UserName());
12465         gameInfo.timeControl = TimeControlTagValue();
12466         break;
12467
12468       case MachinePlaysBlack:
12469         gameInfo.event = StrSave( appData.pgnEventHeader );
12470         gameInfo.site = StrSave(HostName());
12471         gameInfo.date = PGNDate();
12472         gameInfo.round = StrSave("-");
12473         gameInfo.white = StrSave(UserName());
12474         gameInfo.black = StrSave(first.tidy);
12475         gameInfo.timeControl = TimeControlTagValue();
12476         break;
12477
12478       case TwoMachinesPlay:
12479         gameInfo.event = StrSave( appData.pgnEventHeader );
12480         gameInfo.site = StrSave(HostName());
12481         gameInfo.date = PGNDate();
12482         if (matchGame > 0) {
12483             char buf[MSG_SIZ];
12484             sprintf(buf, "%d", matchGame);
12485             gameInfo.round = StrSave(buf);
12486         } else {
12487             gameInfo.round = StrSave("-");
12488         }
12489         if (first.twoMachinesColor[0] == 'w') {
12490             gameInfo.white = StrSave(first.tidy);
12491             gameInfo.black = StrSave(second.tidy);
12492         } else {
12493             gameInfo.white = StrSave(second.tidy);
12494             gameInfo.black = StrSave(first.tidy);
12495         }
12496         gameInfo.timeControl = TimeControlTagValue();
12497         break;
12498
12499       case EditGame:
12500         gameInfo.event = StrSave("Edited game");
12501         gameInfo.site = StrSave(HostName());
12502         gameInfo.date = PGNDate();
12503         gameInfo.round = StrSave("-");
12504         gameInfo.white = StrSave("-");
12505         gameInfo.black = StrSave("-");
12506         gameInfo.result = r;
12507         gameInfo.resultDetails = p;
12508         break;
12509
12510       case EditPosition:
12511         gameInfo.event = StrSave("Edited position");
12512         gameInfo.site = StrSave(HostName());
12513         gameInfo.date = PGNDate();
12514         gameInfo.round = StrSave("-");
12515         gameInfo.white = StrSave("-");
12516         gameInfo.black = StrSave("-");
12517         break;
12518
12519       case IcsPlayingWhite:
12520       case IcsPlayingBlack:
12521       case IcsObserving:
12522       case IcsExamining:
12523         break;
12524
12525       case PlayFromGameFile:
12526         gameInfo.event = StrSave("Game from non-PGN file");
12527         gameInfo.site = StrSave(HostName());
12528         gameInfo.date = PGNDate();
12529         gameInfo.round = StrSave("-");
12530         gameInfo.white = StrSave("?");
12531         gameInfo.black = StrSave("?");
12532         break;
12533
12534       default:
12535         break;
12536     }
12537 }
12538
12539 void
12540 ReplaceComment(index, text)
12541      int index;
12542      char *text;
12543 {
12544     int len;
12545
12546     while (*text == '\n') text++;
12547     len = strlen(text);
12548     while (len > 0 && text[len - 1] == '\n') len--;
12549
12550     if (commentList[index] != NULL)
12551       free(commentList[index]);
12552
12553     if (len == 0) {
12554         commentList[index] = NULL;
12555         return;
12556     }
12557   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12558       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12559       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12560     commentList[index] = (char *) malloc(len + 2);
12561     strncpy(commentList[index], text, len);
12562     commentList[index][len] = '\n';
12563     commentList[index][len + 1] = NULLCHAR;
12564   } else { 
12565     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12566     char *p;
12567     commentList[index] = (char *) malloc(len + 6);
12568     strcpy(commentList[index], "{\n");
12569     strncpy(commentList[index]+2, text, len);
12570     commentList[index][len+2] = NULLCHAR;
12571     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12572     strcat(commentList[index], "\n}\n");
12573   }
12574 }
12575
12576 void
12577 CrushCRs(text)
12578      char *text;
12579 {
12580   char *p = text;
12581   char *q = text;
12582   char ch;
12583
12584   do {
12585     ch = *p++;
12586     if (ch == '\r') continue;
12587     *q++ = ch;
12588   } while (ch != '\0');
12589 }
12590
12591 void
12592 AppendComment(index, text, addBraces)
12593      int index;
12594      char *text;
12595      Boolean addBraces; // [HGM] braces: tells if we should add {}
12596 {
12597     int oldlen, len;
12598     char *old;
12599
12600 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12601     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12602
12603     CrushCRs(text);
12604     while (*text == '\n') text++;
12605     len = strlen(text);
12606     while (len > 0 && text[len - 1] == '\n') len--;
12607
12608     if (len == 0) return;
12609
12610     if (commentList[index] != NULL) {
12611         old = commentList[index];
12612         oldlen = strlen(old);
12613         while(commentList[index][oldlen-1] ==  '\n')
12614           commentList[index][--oldlen] = NULLCHAR;
12615         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12616         strcpy(commentList[index], old);
12617         free(old);
12618         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12619         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12620           if(addBraces) addBraces = FALSE; else { text++; len--; }
12621           while (*text == '\n') { text++; len--; }
12622           commentList[index][--oldlen] = NULLCHAR;
12623       }
12624         if(addBraces) strcat(commentList[index], "\n{\n");
12625         else          strcat(commentList[index], "\n");
12626         strcat(commentList[index], text);
12627         if(addBraces) strcat(commentList[index], "\n}\n");
12628         else          strcat(commentList[index], "\n");
12629     } else {
12630         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12631         if(addBraces)
12632              strcpy(commentList[index], "{\n");
12633         else commentList[index][0] = NULLCHAR;
12634         strcat(commentList[index], text);
12635         strcat(commentList[index], "\n");
12636         if(addBraces) strcat(commentList[index], "}\n");
12637     }
12638 }
12639
12640 static char * FindStr( char * text, char * sub_text )
12641 {
12642     char * result = strstr( text, sub_text );
12643
12644     if( result != NULL ) {
12645         result += strlen( sub_text );
12646     }
12647
12648     return result;
12649 }
12650
12651 /* [AS] Try to extract PV info from PGN comment */
12652 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12653 char *GetInfoFromComment( int index, char * text )
12654 {
12655     char * sep = text;
12656
12657     if( text != NULL && index > 0 ) {
12658         int score = 0;
12659         int depth = 0;
12660         int time = -1, sec = 0, deci;
12661         char * s_eval = FindStr( text, "[%eval " );
12662         char * s_emt = FindStr( text, "[%emt " );
12663
12664         if( s_eval != NULL || s_emt != NULL ) {
12665             /* New style */
12666             char delim;
12667
12668             if( s_eval != NULL ) {
12669                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12670                     return text;
12671                 }
12672
12673                 if( delim != ']' ) {
12674                     return text;
12675                 }
12676             }
12677
12678             if( s_emt != NULL ) {
12679             }
12680                 return text;
12681         }
12682         else {
12683             /* We expect something like: [+|-]nnn.nn/dd */
12684             int score_lo = 0;
12685
12686             if(*text != '{') return text; // [HGM] braces: must be normal comment
12687
12688             sep = strchr( text, '/' );
12689             if( sep == NULL || sep < (text+4) ) {
12690                 return text;
12691             }
12692
12693             time = -1; sec = -1; deci = -1;
12694             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12695                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12696                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12697                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12698                 return text;
12699             }
12700
12701             if( score_lo < 0 || score_lo >= 100 ) {
12702                 return text;
12703             }
12704
12705             if(sec >= 0) time = 600*time + 10*sec; else
12706             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12707
12708             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12709
12710             /* [HGM] PV time: now locate end of PV info */
12711             while( *++sep >= '0' && *sep <= '9'); // strip depth
12712             if(time >= 0)
12713             while( *++sep >= '0' && *sep <= '9'); // strip time
12714             if(sec >= 0)
12715             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12716             if(deci >= 0)
12717             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12718             while(*sep == ' ') sep++;
12719         }
12720
12721         if( depth <= 0 ) {
12722             return text;
12723         }
12724
12725         if( time < 0 ) {
12726             time = -1;
12727         }
12728
12729         pvInfoList[index-1].depth = depth;
12730         pvInfoList[index-1].score = score;
12731         pvInfoList[index-1].time  = 10*time; // centi-sec
12732         if(*sep == '}') *sep = 0; else *--sep = '{';
12733     }
12734     return sep;
12735 }
12736
12737 void
12738 SendToProgram(message, cps)
12739      char *message;
12740      ChessProgramState *cps;
12741 {
12742     int count, outCount, error;
12743     char buf[MSG_SIZ];
12744
12745     if (cps->pr == NULL) return;
12746     Attention(cps);
12747     
12748     if (appData.debugMode) {
12749         TimeMark now;
12750         GetTimeMark(&now);
12751         fprintf(debugFP, "%ld >%-6s: %s", 
12752                 SubtractTimeMarks(&now, &programStartTime),
12753                 cps->which, message);
12754     }
12755     
12756     count = strlen(message);
12757     outCount = OutputToProcess(cps->pr, message, count, &error);
12758     if (outCount < count && !exiting 
12759                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12760         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12761         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12762             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12763                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12764                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12765             } else {
12766                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12767             }
12768             gameInfo.resultDetails = StrSave(buf);
12769         }
12770         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12771     }
12772 }
12773
12774 void
12775 ReceiveFromProgram(isr, closure, message, count, error)
12776      InputSourceRef isr;
12777      VOIDSTAR closure;
12778      char *message;
12779      int count;
12780      int error;
12781 {
12782     char *end_str;
12783     char buf[MSG_SIZ];
12784     ChessProgramState *cps = (ChessProgramState *)closure;
12785
12786     if (isr != cps->isr) return; /* Killed intentionally */
12787     if (count <= 0) {
12788         if (count == 0) {
12789             sprintf(buf,
12790                     _("Error: %s chess program (%s) exited unexpectedly"),
12791                     cps->which, cps->program);
12792         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12793                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12794                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12795                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12796                 } else {
12797                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12798                 }
12799                 gameInfo.resultDetails = StrSave(buf);
12800             }
12801             RemoveInputSource(cps->isr);
12802             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12803         } else {
12804             sprintf(buf,
12805                     _("Error reading from %s chess program (%s)"),
12806                     cps->which, cps->program);
12807             RemoveInputSource(cps->isr);
12808
12809             /* [AS] Program is misbehaving badly... kill it */
12810             if( count == -2 ) {
12811                 DestroyChildProcess( cps->pr, 9 );
12812                 cps->pr = NoProc;
12813             }
12814
12815             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12816         }
12817         return;
12818     }
12819     
12820     if ((end_str = strchr(message, '\r')) != NULL)
12821       *end_str = NULLCHAR;
12822     if ((end_str = strchr(message, '\n')) != NULL)
12823       *end_str = NULLCHAR;
12824     
12825     if (appData.debugMode) {
12826         TimeMark now; int print = 1;
12827         char *quote = ""; char c; int i;
12828
12829         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12830                 char start = message[0];
12831                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12832                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12833                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12834                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12835                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12836                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12837                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12838                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12839                         { quote = "# "; print = (appData.engineComments == 2); }
12840                 message[0] = start; // restore original message
12841         }
12842         if(print) {
12843                 GetTimeMark(&now);
12844                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12845                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12846                         quote,
12847                         message);
12848         }
12849     }
12850
12851     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12852     if (appData.icsEngineAnalyze) {
12853         if (strstr(message, "whisper") != NULL ||
12854              strstr(message, "kibitz") != NULL || 
12855             strstr(message, "tellics") != NULL) return;
12856     }
12857
12858     HandleMachineMove(message, cps);
12859 }
12860
12861
12862 void
12863 SendTimeControl(cps, mps, tc, inc, sd, st)
12864      ChessProgramState *cps;
12865      int mps, inc, sd, st;
12866      long tc;
12867 {
12868     char buf[MSG_SIZ];
12869     int seconds;
12870
12871     if( timeControl_2 > 0 ) {
12872         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12873             tc = timeControl_2;
12874         }
12875     }
12876     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12877     inc /= cps->timeOdds;
12878     st  /= cps->timeOdds;
12879
12880     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12881
12882     if (st > 0) {
12883       /* Set exact time per move, normally using st command */
12884       if (cps->stKludge) {
12885         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12886         seconds = st % 60;
12887         if (seconds == 0) {
12888           sprintf(buf, "level 1 %d\n", st/60);
12889         } else {
12890           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12891         }
12892       } else {
12893         sprintf(buf, "st %d\n", st);
12894       }
12895     } else {
12896       /* Set conventional or incremental time control, using level command */
12897       if (seconds == 0) {
12898         /* Note old gnuchess bug -- minutes:seconds used to not work.
12899            Fixed in later versions, but still avoid :seconds
12900            when seconds is 0. */
12901         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12902       } else {
12903         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12904                 seconds, inc/1000);
12905       }
12906     }
12907     SendToProgram(buf, cps);
12908
12909     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12910     /* Orthogonally, limit search to given depth */
12911     if (sd > 0) {
12912       if (cps->sdKludge) {
12913         sprintf(buf, "depth\n%d\n", sd);
12914       } else {
12915         sprintf(buf, "sd %d\n", sd);
12916       }
12917       SendToProgram(buf, cps);
12918     }
12919
12920     if(cps->nps > 0) { /* [HGM] nps */
12921         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12922         else {
12923                 sprintf(buf, "nps %d\n", cps->nps);
12924               SendToProgram(buf, cps);
12925         }
12926     }
12927 }
12928
12929 ChessProgramState *WhitePlayer()
12930 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12931 {
12932     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12933        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12934         return &second;
12935     return &first;
12936 }
12937
12938 void
12939 SendTimeRemaining(cps, machineWhite)
12940      ChessProgramState *cps;
12941      int /*boolean*/ machineWhite;
12942 {
12943     char message[MSG_SIZ];
12944     long time, otime;
12945
12946     /* Note: this routine must be called when the clocks are stopped
12947        or when they have *just* been set or switched; otherwise
12948        it will be off by the time since the current tick started.
12949     */
12950     if (machineWhite) {
12951         time = whiteTimeRemaining / 10;
12952         otime = blackTimeRemaining / 10;
12953     } else {
12954         time = blackTimeRemaining / 10;
12955         otime = whiteTimeRemaining / 10;
12956     }
12957     /* [HGM] translate opponent's time by time-odds factor */
12958     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12959     if (appData.debugMode) {
12960         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12961     }
12962
12963     if (time <= 0) time = 1;
12964     if (otime <= 0) otime = 1;
12965     
12966     sprintf(message, "time %ld\n", time);
12967     SendToProgram(message, cps);
12968
12969     sprintf(message, "otim %ld\n", otime);
12970     SendToProgram(message, cps);
12971 }
12972
12973 int
12974 BoolFeature(p, name, loc, cps)
12975      char **p;
12976      char *name;
12977      int *loc;
12978      ChessProgramState *cps;
12979 {
12980   char buf[MSG_SIZ];
12981   int len = strlen(name);
12982   int val;
12983   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12984     (*p) += len + 1;
12985     sscanf(*p, "%d", &val);
12986     *loc = (val != 0);
12987     while (**p && **p != ' ') (*p)++;
12988     sprintf(buf, "accepted %s\n", name);
12989     SendToProgram(buf, cps);
12990     return TRUE;
12991   }
12992   return FALSE;
12993 }
12994
12995 int
12996 IntFeature(p, name, loc, cps)
12997      char **p;
12998      char *name;
12999      int *loc;
13000      ChessProgramState *cps;
13001 {
13002   char buf[MSG_SIZ];
13003   int len = strlen(name);
13004   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13005     (*p) += len + 1;
13006     sscanf(*p, "%d", loc);
13007     while (**p && **p != ' ') (*p)++;
13008     sprintf(buf, "accepted %s\n", name);
13009     SendToProgram(buf, cps);
13010     return TRUE;
13011   }
13012   return FALSE;
13013 }
13014
13015 int
13016 StringFeature(p, name, loc, cps)
13017      char **p;
13018      char *name;
13019      char loc[];
13020      ChessProgramState *cps;
13021 {
13022   char buf[MSG_SIZ];
13023   int len = strlen(name);
13024   if (strncmp((*p), name, len) == 0
13025       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13026     (*p) += len + 2;
13027     sscanf(*p, "%[^\"]", loc);
13028     while (**p && **p != '\"') (*p)++;
13029     if (**p == '\"') (*p)++;
13030     sprintf(buf, "accepted %s\n", name);
13031     SendToProgram(buf, cps);
13032     return TRUE;
13033   }
13034   return FALSE;
13035 }
13036
13037 int 
13038 ParseOption(Option *opt, ChessProgramState *cps)
13039 // [HGM] options: process the string that defines an engine option, and determine
13040 // name, type, default value, and allowed value range
13041 {
13042         char *p, *q, buf[MSG_SIZ];
13043         int n, min = (-1)<<31, max = 1<<31, def;
13044
13045         if(p = strstr(opt->name, " -spin ")) {
13046             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13047             if(max < min) max = min; // enforce consistency
13048             if(def < min) def = min;
13049             if(def > max) def = max;
13050             opt->value = def;
13051             opt->min = min;
13052             opt->max = max;
13053             opt->type = Spin;
13054         } else if((p = strstr(opt->name, " -slider "))) {
13055             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13056             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13057             if(max < min) max = min; // enforce consistency
13058             if(def < min) def = min;
13059             if(def > max) def = max;
13060             opt->value = def;
13061             opt->min = min;
13062             opt->max = max;
13063             opt->type = Spin; // Slider;
13064         } else if((p = strstr(opt->name, " -string "))) {
13065             opt->textValue = p+9;
13066             opt->type = TextBox;
13067         } else if((p = strstr(opt->name, " -file "))) {
13068             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13069             opt->textValue = p+7;
13070             opt->type = TextBox; // FileName;
13071         } else if((p = strstr(opt->name, " -path "))) {
13072             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13073             opt->textValue = p+7;
13074             opt->type = TextBox; // PathName;
13075         } else if(p = strstr(opt->name, " -check ")) {
13076             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13077             opt->value = (def != 0);
13078             opt->type = CheckBox;
13079         } else if(p = strstr(opt->name, " -combo ")) {
13080             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13081             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13082             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13083             opt->value = n = 0;
13084             while(q = StrStr(q, " /// ")) {
13085                 n++; *q = 0;    // count choices, and null-terminate each of them
13086                 q += 5;
13087                 if(*q == '*') { // remember default, which is marked with * prefix
13088                     q++;
13089                     opt->value = n;
13090                 }
13091                 cps->comboList[cps->comboCnt++] = q;
13092             }
13093             cps->comboList[cps->comboCnt++] = NULL;
13094             opt->max = n + 1;
13095             opt->type = ComboBox;
13096         } else if(p = strstr(opt->name, " -button")) {
13097             opt->type = Button;
13098         } else if(p = strstr(opt->name, " -save")) {
13099             opt->type = SaveButton;
13100         } else return FALSE;
13101         *p = 0; // terminate option name
13102         // now look if the command-line options define a setting for this engine option.
13103         if(cps->optionSettings && cps->optionSettings[0])
13104             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13105         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13106                 sprintf(buf, "option %s", p);
13107                 if(p = strstr(buf, ",")) *p = 0;
13108                 strcat(buf, "\n");
13109                 SendToProgram(buf, cps);
13110         }
13111         return TRUE;
13112 }
13113
13114 void
13115 FeatureDone(cps, val)
13116      ChessProgramState* cps;
13117      int val;
13118 {
13119   DelayedEventCallback cb = GetDelayedEvent();
13120   if ((cb == InitBackEnd3 && cps == &first) ||
13121       (cb == TwoMachinesEventIfReady && cps == &second)) {
13122     CancelDelayedEvent();
13123     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13124   }
13125   cps->initDone = val;
13126 }
13127
13128 /* Parse feature command from engine */
13129 void
13130 ParseFeatures(args, cps)
13131      char* args;
13132      ChessProgramState *cps;  
13133 {
13134   char *p = args;
13135   char *q;
13136   int val;
13137   char buf[MSG_SIZ];
13138
13139   for (;;) {
13140     while (*p == ' ') p++;
13141     if (*p == NULLCHAR) return;
13142
13143     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13144     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13145     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13146     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13147     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13148     if (BoolFeature(&p, "reuse", &val, cps)) {
13149       /* Engine can disable reuse, but can't enable it if user said no */
13150       if (!val) cps->reuse = FALSE;
13151       continue;
13152     }
13153     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13154     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13155       if (gameMode == TwoMachinesPlay) {
13156         DisplayTwoMachinesTitle();
13157       } else {
13158         DisplayTitle("");
13159       }
13160       continue;
13161     }
13162     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13163     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13164     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13165     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13166     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13167     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13168     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13169     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13170     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13171     if (IntFeature(&p, "done", &val, cps)) {
13172       FeatureDone(cps, val);
13173       continue;
13174     }
13175     /* Added by Tord: */
13176     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13177     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13178     /* End of additions by Tord */
13179
13180     /* [HGM] added features: */
13181     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13182     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13183     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13184     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13185     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13186     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13187     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13188         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13189             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13190             SendToProgram(buf, cps);
13191             continue;
13192         }
13193         if(cps->nrOptions >= MAX_OPTIONS) {
13194             cps->nrOptions--;
13195             sprintf(buf, "%s engine has too many options\n", cps->which);
13196             DisplayError(buf, 0);
13197         }
13198         continue;
13199     }
13200     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13201     /* End of additions by HGM */
13202
13203     /* unknown feature: complain and skip */
13204     q = p;
13205     while (*q && *q != '=') q++;
13206     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13207     SendToProgram(buf, cps);
13208     p = q;
13209     if (*p == '=') {
13210       p++;
13211       if (*p == '\"') {
13212         p++;
13213         while (*p && *p != '\"') p++;
13214         if (*p == '\"') p++;
13215       } else {
13216         while (*p && *p != ' ') p++;
13217       }
13218     }
13219   }
13220
13221 }
13222
13223 void
13224 PeriodicUpdatesEvent(newState)
13225      int newState;
13226 {
13227     if (newState == appData.periodicUpdates)
13228       return;
13229
13230     appData.periodicUpdates=newState;
13231
13232     /* Display type changes, so update it now */
13233 //    DisplayAnalysis();
13234
13235     /* Get the ball rolling again... */
13236     if (newState) {
13237         AnalysisPeriodicEvent(1);
13238         StartAnalysisClock();
13239     }
13240 }
13241
13242 void
13243 PonderNextMoveEvent(newState)
13244      int newState;
13245 {
13246     if (newState == appData.ponderNextMove) return;
13247     if (gameMode == EditPosition) EditPositionDone(TRUE);
13248     if (newState) {
13249         SendToProgram("hard\n", &first);
13250         if (gameMode == TwoMachinesPlay) {
13251             SendToProgram("hard\n", &second);
13252         }
13253     } else {
13254         SendToProgram("easy\n", &first);
13255         thinkOutput[0] = NULLCHAR;
13256         if (gameMode == TwoMachinesPlay) {
13257             SendToProgram("easy\n", &second);
13258         }
13259     }
13260     appData.ponderNextMove = newState;
13261 }
13262
13263 void
13264 NewSettingEvent(option, command, value)
13265      char *command;
13266      int option, value;
13267 {
13268     char buf[MSG_SIZ];
13269
13270     if (gameMode == EditPosition) EditPositionDone(TRUE);
13271     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13272     SendToProgram(buf, &first);
13273     if (gameMode == TwoMachinesPlay) {
13274         SendToProgram(buf, &second);
13275     }
13276 }
13277
13278 void
13279 ShowThinkingEvent()
13280 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13281 {
13282     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13283     int newState = appData.showThinking
13284         // [HGM] thinking: other features now need thinking output as well
13285         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13286     
13287     if (oldState == newState) return;
13288     oldState = newState;
13289     if (gameMode == EditPosition) EditPositionDone(TRUE);
13290     if (oldState) {
13291         SendToProgram("post\n", &first);
13292         if (gameMode == TwoMachinesPlay) {
13293             SendToProgram("post\n", &second);
13294         }
13295     } else {
13296         SendToProgram("nopost\n", &first);
13297         thinkOutput[0] = NULLCHAR;
13298         if (gameMode == TwoMachinesPlay) {
13299             SendToProgram("nopost\n", &second);
13300         }
13301     }
13302 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13303 }
13304
13305 void
13306 AskQuestionEvent(title, question, replyPrefix, which)
13307      char *title; char *question; char *replyPrefix; char *which;
13308 {
13309   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13310   if (pr == NoProc) return;
13311   AskQuestion(title, question, replyPrefix, pr);
13312 }
13313
13314 void
13315 DisplayMove(moveNumber)
13316      int moveNumber;
13317 {
13318     char message[MSG_SIZ];
13319     char res[MSG_SIZ];
13320     char cpThinkOutput[MSG_SIZ];
13321
13322     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13323     
13324     if (moveNumber == forwardMostMove - 1 || 
13325         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13326
13327         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13328
13329         if (strchr(cpThinkOutput, '\n')) {
13330             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13331         }
13332     } else {
13333         *cpThinkOutput = NULLCHAR;
13334     }
13335
13336     /* [AS] Hide thinking from human user */
13337     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13338         *cpThinkOutput = NULLCHAR;
13339         if( thinkOutput[0] != NULLCHAR ) {
13340             int i;
13341
13342             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13343                 cpThinkOutput[i] = '.';
13344             }
13345             cpThinkOutput[i] = NULLCHAR;
13346             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13347         }
13348     }
13349
13350     if (moveNumber == forwardMostMove - 1 &&
13351         gameInfo.resultDetails != NULL) {
13352         if (gameInfo.resultDetails[0] == NULLCHAR) {
13353             sprintf(res, " %s", PGNResult(gameInfo.result));
13354         } else {
13355             sprintf(res, " {%s} %s",
13356                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13357         }
13358     } else {
13359         res[0] = NULLCHAR;
13360     }
13361
13362     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13363         DisplayMessage(res, cpThinkOutput);
13364     } else {
13365         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13366                 WhiteOnMove(moveNumber) ? " " : ".. ",
13367                 parseList[moveNumber], res);
13368         DisplayMessage(message, cpThinkOutput);
13369     }
13370 }
13371
13372 void
13373 DisplayComment(moveNumber, text)
13374      int moveNumber;
13375      char *text;
13376 {
13377     char title[MSG_SIZ];
13378     char buf[8000]; // comment can be long!
13379     int score, depth;
13380     
13381     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13382       strcpy(title, "Comment");
13383     } else {
13384       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13385               WhiteOnMove(moveNumber) ? " " : ".. ",
13386               parseList[moveNumber]);
13387     }
13388     // [HGM] PV info: display PV info together with (or as) comment
13389     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13390       if(text == NULL) text = "";                                           
13391       score = pvInfoList[moveNumber].score;
13392       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13393               depth, (pvInfoList[moveNumber].time+50)/100, text);
13394       text = buf;
13395     }
13396     if (text != NULL && (appData.autoDisplayComment || commentUp))
13397         CommentPopUp(title, text);
13398 }
13399
13400 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13401  * might be busy thinking or pondering.  It can be omitted if your
13402  * gnuchess is configured to stop thinking immediately on any user
13403  * input.  However, that gnuchess feature depends on the FIONREAD
13404  * ioctl, which does not work properly on some flavors of Unix.
13405  */
13406 void
13407 Attention(cps)
13408      ChessProgramState *cps;
13409 {
13410 #if ATTENTION
13411     if (!cps->useSigint) return;
13412     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13413     switch (gameMode) {
13414       case MachinePlaysWhite:
13415       case MachinePlaysBlack:
13416       case TwoMachinesPlay:
13417       case IcsPlayingWhite:
13418       case IcsPlayingBlack:
13419       case AnalyzeMode:
13420       case AnalyzeFile:
13421         /* Skip if we know it isn't thinking */
13422         if (!cps->maybeThinking) return;
13423         if (appData.debugMode)
13424           fprintf(debugFP, "Interrupting %s\n", cps->which);
13425         InterruptChildProcess(cps->pr);
13426         cps->maybeThinking = FALSE;
13427         break;
13428       default:
13429         break;
13430     }
13431 #endif /*ATTENTION*/
13432 }
13433
13434 int
13435 CheckFlags()
13436 {
13437     if (whiteTimeRemaining <= 0) {
13438         if (!whiteFlag) {
13439             whiteFlag = TRUE;
13440             if (appData.icsActive) {
13441                 if (appData.autoCallFlag &&
13442                     gameMode == IcsPlayingBlack && !blackFlag) {
13443                   SendToICS(ics_prefix);
13444                   SendToICS("flag\n");
13445                 }
13446             } else {
13447                 if (blackFlag) {
13448                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13449                 } else {
13450                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13451                     if (appData.autoCallFlag) {
13452                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13453                         return TRUE;
13454                     }
13455                 }
13456             }
13457         }
13458     }
13459     if (blackTimeRemaining <= 0) {
13460         if (!blackFlag) {
13461             blackFlag = TRUE;
13462             if (appData.icsActive) {
13463                 if (appData.autoCallFlag &&
13464                     gameMode == IcsPlayingWhite && !whiteFlag) {
13465                   SendToICS(ics_prefix);
13466                   SendToICS("flag\n");
13467                 }
13468             } else {
13469                 if (whiteFlag) {
13470                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13471                 } else {
13472                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13473                     if (appData.autoCallFlag) {
13474                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13475                         return TRUE;
13476                     }
13477                 }
13478             }
13479         }
13480     }
13481     return FALSE;
13482 }
13483
13484 void
13485 CheckTimeControl()
13486 {
13487     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13488         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13489
13490     /*
13491      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13492      */
13493     if ( !WhiteOnMove(forwardMostMove) )
13494         /* White made time control */
13495         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13496         /* [HGM] time odds: correct new time quota for time odds! */
13497                                             / WhitePlayer()->timeOdds;
13498       else
13499         /* Black made time control */
13500         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13501                                             / WhitePlayer()->other->timeOdds;
13502 }
13503
13504 void
13505 DisplayBothClocks()
13506 {
13507     int wom = gameMode == EditPosition ?
13508       !blackPlaysFirst : WhiteOnMove(currentMove);
13509     DisplayWhiteClock(whiteTimeRemaining, wom);
13510     DisplayBlackClock(blackTimeRemaining, !wom);
13511 }
13512
13513
13514 /* Timekeeping seems to be a portability nightmare.  I think everyone
13515    has ftime(), but I'm really not sure, so I'm including some ifdefs
13516    to use other calls if you don't.  Clocks will be less accurate if
13517    you have neither ftime nor gettimeofday.
13518 */
13519
13520 /* VS 2008 requires the #include outside of the function */
13521 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13522 #include <sys/timeb.h>
13523 #endif
13524
13525 /* Get the current time as a TimeMark */
13526 void
13527 GetTimeMark(tm)
13528      TimeMark *tm;
13529 {
13530 #if HAVE_GETTIMEOFDAY
13531
13532     struct timeval timeVal;
13533     struct timezone timeZone;
13534
13535     gettimeofday(&timeVal, &timeZone);
13536     tm->sec = (long) timeVal.tv_sec; 
13537     tm->ms = (int) (timeVal.tv_usec / 1000L);
13538
13539 #else /*!HAVE_GETTIMEOFDAY*/
13540 #if HAVE_FTIME
13541
13542 // include <sys/timeb.h> / moved to just above start of function
13543     struct timeb timeB;
13544
13545     ftime(&timeB);
13546     tm->sec = (long) timeB.time;
13547     tm->ms = (int) timeB.millitm;
13548
13549 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13550     tm->sec = (long) time(NULL);
13551     tm->ms = 0;
13552 #endif
13553 #endif
13554 }
13555
13556 /* Return the difference in milliseconds between two
13557    time marks.  We assume the difference will fit in a long!
13558 */
13559 long
13560 SubtractTimeMarks(tm2, tm1)
13561      TimeMark *tm2, *tm1;
13562 {
13563     return 1000L*(tm2->sec - tm1->sec) +
13564            (long) (tm2->ms - tm1->ms);
13565 }
13566
13567
13568 /*
13569  * Code to manage the game clocks.
13570  *
13571  * In tournament play, black starts the clock and then white makes a move.
13572  * We give the human user a slight advantage if he is playing white---the
13573  * clocks don't run until he makes his first move, so it takes zero time.
13574  * Also, we don't account for network lag, so we could get out of sync
13575  * with GNU Chess's clock -- but then, referees are always right.  
13576  */
13577
13578 static TimeMark tickStartTM;
13579 static long intendedTickLength;
13580
13581 long
13582 NextTickLength(timeRemaining)
13583      long timeRemaining;
13584 {
13585     long nominalTickLength, nextTickLength;
13586
13587     if (timeRemaining > 0L && timeRemaining <= 10000L)
13588       nominalTickLength = 100L;
13589     else
13590       nominalTickLength = 1000L;
13591     nextTickLength = timeRemaining % nominalTickLength;
13592     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13593
13594     return nextTickLength;
13595 }
13596
13597 /* Adjust clock one minute up or down */
13598 void
13599 AdjustClock(Boolean which, int dir)
13600 {
13601     if(which) blackTimeRemaining += 60000*dir;
13602     else      whiteTimeRemaining += 60000*dir;
13603     DisplayBothClocks();
13604 }
13605
13606 /* Stop clocks and reset to a fresh time control */
13607 void
13608 ResetClocks() 
13609 {
13610     (void) StopClockTimer();
13611     if (appData.icsActive) {
13612         whiteTimeRemaining = blackTimeRemaining = 0;
13613     } else if (searchTime) {
13614         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13615         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13616     } else { /* [HGM] correct new time quote for time odds */
13617         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13618         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13619     }
13620     if (whiteFlag || blackFlag) {
13621         DisplayTitle("");
13622         whiteFlag = blackFlag = FALSE;
13623     }
13624     DisplayBothClocks();
13625 }
13626
13627 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13628
13629 /* Decrement running clock by amount of time that has passed */
13630 void
13631 DecrementClocks()
13632 {
13633     long timeRemaining;
13634     long lastTickLength, fudge;
13635     TimeMark now;
13636
13637     if (!appData.clockMode) return;
13638     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13639         
13640     GetTimeMark(&now);
13641
13642     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13643
13644     /* Fudge if we woke up a little too soon */
13645     fudge = intendedTickLength - lastTickLength;
13646     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13647
13648     if (WhiteOnMove(forwardMostMove)) {
13649         if(whiteNPS >= 0) lastTickLength = 0;
13650         timeRemaining = whiteTimeRemaining -= lastTickLength;
13651         DisplayWhiteClock(whiteTimeRemaining - fudge,
13652                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13653     } else {
13654         if(blackNPS >= 0) lastTickLength = 0;
13655         timeRemaining = blackTimeRemaining -= lastTickLength;
13656         DisplayBlackClock(blackTimeRemaining - fudge,
13657                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13658     }
13659
13660     if (CheckFlags()) return;
13661         
13662     tickStartTM = now;
13663     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13664     StartClockTimer(intendedTickLength);
13665
13666     /* if the time remaining has fallen below the alarm threshold, sound the
13667      * alarm. if the alarm has sounded and (due to a takeback or time control
13668      * with increment) the time remaining has increased to a level above the
13669      * threshold, reset the alarm so it can sound again. 
13670      */
13671     
13672     if (appData.icsActive && appData.icsAlarm) {
13673
13674         /* make sure we are dealing with the user's clock */
13675         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13676                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13677            )) return;
13678
13679         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13680             alarmSounded = FALSE;
13681         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13682             PlayAlarmSound();
13683             alarmSounded = TRUE;
13684         }
13685     }
13686 }
13687
13688
13689 /* A player has just moved, so stop the previously running
13690    clock and (if in clock mode) start the other one.
13691    We redisplay both clocks in case we're in ICS mode, because
13692    ICS gives us an update to both clocks after every move.
13693    Note that this routine is called *after* forwardMostMove
13694    is updated, so the last fractional tick must be subtracted
13695    from the color that is *not* on move now.
13696 */
13697 void
13698 SwitchClocks()
13699 {
13700     long lastTickLength;
13701     TimeMark now;
13702     int flagged = FALSE;
13703
13704     GetTimeMark(&now);
13705
13706     if (StopClockTimer() && appData.clockMode) {
13707         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13708         if (WhiteOnMove(forwardMostMove)) {
13709             if(blackNPS >= 0) lastTickLength = 0;
13710             blackTimeRemaining -= lastTickLength;
13711            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13712 //         if(pvInfoList[forwardMostMove-1].time == -1)
13713                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13714                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13715         } else {
13716            if(whiteNPS >= 0) lastTickLength = 0;
13717            whiteTimeRemaining -= lastTickLength;
13718            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13719 //         if(pvInfoList[forwardMostMove-1].time == -1)
13720                  pvInfoList[forwardMostMove-1].time = 
13721                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13722         }
13723         flagged = CheckFlags();
13724     }
13725     CheckTimeControl();
13726
13727     if (flagged || !appData.clockMode) return;
13728
13729     switch (gameMode) {
13730       case MachinePlaysBlack:
13731       case MachinePlaysWhite:
13732       case BeginningOfGame:
13733         if (pausing) return;
13734         break;
13735
13736       case EditGame:
13737       case PlayFromGameFile:
13738       case IcsExamining:
13739         return;
13740
13741       default:
13742         break;
13743     }
13744
13745     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13746         if(WhiteOnMove(forwardMostMove))
13747              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13748         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13749     }
13750
13751     tickStartTM = now;
13752     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13753       whiteTimeRemaining : blackTimeRemaining);
13754     StartClockTimer(intendedTickLength);
13755 }
13756         
13757
13758 /* Stop both clocks */
13759 void
13760 StopClocks()
13761 {       
13762     long lastTickLength;
13763     TimeMark now;
13764
13765     if (!StopClockTimer()) return;
13766     if (!appData.clockMode) return;
13767
13768     GetTimeMark(&now);
13769
13770     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13771     if (WhiteOnMove(forwardMostMove)) {
13772         if(whiteNPS >= 0) lastTickLength = 0;
13773         whiteTimeRemaining -= lastTickLength;
13774         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13775     } else {
13776         if(blackNPS >= 0) lastTickLength = 0;
13777         blackTimeRemaining -= lastTickLength;
13778         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13779     }
13780     CheckFlags();
13781 }
13782         
13783 /* Start clock of player on move.  Time may have been reset, so
13784    if clock is already running, stop and restart it. */
13785 void
13786 StartClocks()
13787 {
13788     (void) StopClockTimer(); /* in case it was running already */
13789     DisplayBothClocks();
13790     if (CheckFlags()) return;
13791
13792     if (!appData.clockMode) return;
13793     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13794
13795     GetTimeMark(&tickStartTM);
13796     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13797       whiteTimeRemaining : blackTimeRemaining);
13798
13799    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13800     whiteNPS = blackNPS = -1; 
13801     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13802        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13803         whiteNPS = first.nps;
13804     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13805        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13806         blackNPS = first.nps;
13807     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13808         whiteNPS = second.nps;
13809     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13810         blackNPS = second.nps;
13811     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13812
13813     StartClockTimer(intendedTickLength);
13814 }
13815
13816 char *
13817 TimeString(ms)
13818      long ms;
13819 {
13820     long second, minute, hour, day;
13821     char *sign = "";
13822     static char buf[32];
13823     
13824     if (ms > 0 && ms <= 9900) {
13825       /* convert milliseconds to tenths, rounding up */
13826       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13827
13828       sprintf(buf, " %03.1f ", tenths/10.0);
13829       return buf;
13830     }
13831
13832     /* convert milliseconds to seconds, rounding up */
13833     /* use floating point to avoid strangeness of integer division
13834        with negative dividends on many machines */
13835     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13836
13837     if (second < 0) {
13838         sign = "-";
13839         second = -second;
13840     }
13841     
13842     day = second / (60 * 60 * 24);
13843     second = second % (60 * 60 * 24);
13844     hour = second / (60 * 60);
13845     second = second % (60 * 60);
13846     minute = second / 60;
13847     second = second % 60;
13848     
13849     if (day > 0)
13850       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13851               sign, day, hour, minute, second);
13852     else if (hour > 0)
13853       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13854     else
13855       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13856     
13857     return buf;
13858 }
13859
13860
13861 /*
13862  * This is necessary because some C libraries aren't ANSI C compliant yet.
13863  */
13864 char *
13865 StrStr(string, match)
13866      char *string, *match;
13867 {
13868     int i, length;
13869     
13870     length = strlen(match);
13871     
13872     for (i = strlen(string) - length; i >= 0; i--, string++)
13873       if (!strncmp(match, string, length))
13874         return string;
13875     
13876     return NULL;
13877 }
13878
13879 char *
13880 StrCaseStr(string, match)
13881      char *string, *match;
13882 {
13883     int i, j, length;
13884     
13885     length = strlen(match);
13886     
13887     for (i = strlen(string) - length; i >= 0; i--, string++) {
13888         for (j = 0; j < length; j++) {
13889             if (ToLower(match[j]) != ToLower(string[j]))
13890               break;
13891         }
13892         if (j == length) return string;
13893     }
13894
13895     return NULL;
13896 }
13897
13898 #ifndef _amigados
13899 int
13900 StrCaseCmp(s1, s2)
13901      char *s1, *s2;
13902 {
13903     char c1, c2;
13904     
13905     for (;;) {
13906         c1 = ToLower(*s1++);
13907         c2 = ToLower(*s2++);
13908         if (c1 > c2) return 1;
13909         if (c1 < c2) return -1;
13910         if (c1 == NULLCHAR) return 0;
13911     }
13912 }
13913
13914
13915 int
13916 ToLower(c)
13917      int c;
13918 {
13919     return isupper(c) ? tolower(c) : c;
13920 }
13921
13922
13923 int
13924 ToUpper(c)
13925      int c;
13926 {
13927     return islower(c) ? toupper(c) : c;
13928 }
13929 #endif /* !_amigados    */
13930
13931 char *
13932 StrSave(s)
13933      char *s;
13934 {
13935     char *ret;
13936
13937     if ((ret = (char *) malloc(strlen(s) + 1))) {
13938         strcpy(ret, s);
13939     }
13940     return ret;
13941 }
13942
13943 char *
13944 StrSavePtr(s, savePtr)
13945      char *s, **savePtr;
13946 {
13947     if (*savePtr) {
13948         free(*savePtr);
13949     }
13950     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13951         strcpy(*savePtr, s);
13952     }
13953     return(*savePtr);
13954 }
13955
13956 char *
13957 PGNDate()
13958 {
13959     time_t clock;
13960     struct tm *tm;
13961     char buf[MSG_SIZ];
13962
13963     clock = time((time_t *)NULL);
13964     tm = localtime(&clock);
13965     sprintf(buf, "%04d.%02d.%02d",
13966             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13967     return StrSave(buf);
13968 }
13969
13970
13971 char *
13972 PositionToFEN(move, overrideCastling)
13973      int move;
13974      char *overrideCastling;
13975 {
13976     int i, j, fromX, fromY, toX, toY;
13977     int whiteToPlay;
13978     char buf[128];
13979     char *p, *q;
13980     int emptycount;
13981     ChessSquare piece;
13982
13983     whiteToPlay = (gameMode == EditPosition) ?
13984       !blackPlaysFirst : (move % 2 == 0);
13985     p = buf;
13986
13987     /* Piece placement data */
13988     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13989         emptycount = 0;
13990         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13991             if (boards[move][i][j] == EmptySquare) {
13992                 emptycount++;
13993             } else { ChessSquare piece = boards[move][i][j];
13994                 if (emptycount > 0) {
13995                     if(emptycount<10) /* [HGM] can be >= 10 */
13996                         *p++ = '0' + emptycount;
13997                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13998                     emptycount = 0;
13999                 }
14000                 if(PieceToChar(piece) == '+') {
14001                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14002                     *p++ = '+';
14003                     piece = (ChessSquare)(DEMOTED piece);
14004                 } 
14005                 *p++ = PieceToChar(piece);
14006                 if(p[-1] == '~') {
14007                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14008                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14009                     *p++ = '~';
14010                 }
14011             }
14012         }
14013         if (emptycount > 0) {
14014             if(emptycount<10) /* [HGM] can be >= 10 */
14015                 *p++ = '0' + emptycount;
14016             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14017             emptycount = 0;
14018         }
14019         *p++ = '/';
14020     }
14021     *(p - 1) = ' ';
14022
14023     /* [HGM] print Crazyhouse or Shogi holdings */
14024     if( gameInfo.holdingsWidth ) {
14025         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14026         q = p;
14027         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14028             piece = boards[move][i][BOARD_WIDTH-1];
14029             if( piece != EmptySquare )
14030               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14031                   *p++ = PieceToChar(piece);
14032         }
14033         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14034             piece = boards[move][BOARD_HEIGHT-i-1][0];
14035             if( piece != EmptySquare )
14036               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14037                   *p++ = PieceToChar(piece);
14038         }
14039
14040         if( q == p ) *p++ = '-';
14041         *p++ = ']';
14042         *p++ = ' ';
14043     }
14044
14045     /* Active color */
14046     *p++ = whiteToPlay ? 'w' : 'b';
14047     *p++ = ' ';
14048
14049   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14050     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14051   } else {
14052   if(nrCastlingRights) {
14053      q = p;
14054      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14055        /* [HGM] write directly from rights */
14056            if(boards[move][CASTLING][2] != NoRights &&
14057               boards[move][CASTLING][0] != NoRights   )
14058                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14059            if(boards[move][CASTLING][2] != NoRights &&
14060               boards[move][CASTLING][1] != NoRights   )
14061                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14062            if(boards[move][CASTLING][5] != NoRights &&
14063               boards[move][CASTLING][3] != NoRights   )
14064                 *p++ = boards[move][CASTLING][3] + AAA;
14065            if(boards[move][CASTLING][5] != NoRights &&
14066               boards[move][CASTLING][4] != NoRights   )
14067                 *p++ = boards[move][CASTLING][4] + AAA;
14068      } else {
14069
14070         /* [HGM] write true castling rights */
14071         if( nrCastlingRights == 6 ) {
14072             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14073                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14074             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14075                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14076             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14077                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14078             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14079                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14080         }
14081      }
14082      if (q == p) *p++ = '-'; /* No castling rights */
14083      *p++ = ' ';
14084   }
14085
14086   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14087      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14088     /* En passant target square */
14089     if (move > backwardMostMove) {
14090         fromX = moveList[move - 1][0] - AAA;
14091         fromY = moveList[move - 1][1] - ONE;
14092         toX = moveList[move - 1][2] - AAA;
14093         toY = moveList[move - 1][3] - ONE;
14094         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14095             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14096             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14097             fromX == toX) {
14098             /* 2-square pawn move just happened */
14099             *p++ = toX + AAA;
14100             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14101         } else {
14102             *p++ = '-';
14103         }
14104     } else if(move == backwardMostMove) {
14105         // [HGM] perhaps we should always do it like this, and forget the above?
14106         if((signed char)boards[move][EP_STATUS] >= 0) {
14107             *p++ = boards[move][EP_STATUS] + AAA;
14108             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14109         } else {
14110             *p++ = '-';
14111         }
14112     } else {
14113         *p++ = '-';
14114     }
14115     *p++ = ' ';
14116   }
14117   }
14118
14119     /* [HGM] find reversible plies */
14120     {   int i = 0, j=move;
14121
14122         if (appData.debugMode) { int k;
14123             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14124             for(k=backwardMostMove; k<=forwardMostMove; k++)
14125                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14126
14127         }
14128
14129         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14130         if( j == backwardMostMove ) i += initialRulePlies;
14131         sprintf(p, "%d ", i);
14132         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14133     }
14134     /* Fullmove number */
14135     sprintf(p, "%d", (move / 2) + 1);
14136     
14137     return StrSave(buf);
14138 }
14139
14140 Boolean
14141 ParseFEN(board, blackPlaysFirst, fen)
14142     Board board;
14143      int *blackPlaysFirst;
14144      char *fen;
14145 {
14146     int i, j;
14147     char *p;
14148     int emptycount;
14149     ChessSquare piece;
14150
14151     p = fen;
14152
14153     /* [HGM] by default clear Crazyhouse holdings, if present */
14154     if(gameInfo.holdingsWidth) {
14155        for(i=0; i<BOARD_HEIGHT; i++) {
14156            board[i][0]             = EmptySquare; /* black holdings */
14157            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14158            board[i][1]             = (ChessSquare) 0; /* black counts */
14159            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14160        }
14161     }
14162
14163     /* Piece placement data */
14164     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14165         j = 0;
14166         for (;;) {
14167             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14168                 if (*p == '/') p++;
14169                 emptycount = gameInfo.boardWidth - j;
14170                 while (emptycount--)
14171                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14172                 break;
14173 #if(BOARD_FILES >= 10)
14174             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14175                 p++; emptycount=10;
14176                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14177                 while (emptycount--)
14178                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14179 #endif
14180             } else if (isdigit(*p)) {
14181                 emptycount = *p++ - '0';
14182                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14183                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14184                 while (emptycount--)
14185                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14186             } else if (*p == '+' || isalpha(*p)) {
14187                 if (j >= gameInfo.boardWidth) return FALSE;
14188                 if(*p=='+') {
14189                     piece = CharToPiece(*++p);
14190                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14191                     piece = (ChessSquare) (PROMOTED piece ); p++;
14192                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14193                 } else piece = CharToPiece(*p++);
14194
14195                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14196                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14197                     piece = (ChessSquare) (PROMOTED piece);
14198                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14199                     p++;
14200                 }
14201                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14202             } else {
14203                 return FALSE;
14204             }
14205         }
14206     }
14207     while (*p == '/' || *p == ' ') p++;
14208
14209     /* [HGM] look for Crazyhouse holdings here */
14210     while(*p==' ') p++;
14211     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14212         if(*p == '[') p++;
14213         if(*p == '-' ) *p++; /* empty holdings */ else {
14214             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14215             /* if we would allow FEN reading to set board size, we would   */
14216             /* have to add holdings and shift the board read so far here   */
14217             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14218                 *p++;
14219                 if((int) piece >= (int) BlackPawn ) {
14220                     i = (int)piece - (int)BlackPawn;
14221                     i = PieceToNumber((ChessSquare)i);
14222                     if( i >= gameInfo.holdingsSize ) return FALSE;
14223                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14224                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14225                 } else {
14226                     i = (int)piece - (int)WhitePawn;
14227                     i = PieceToNumber((ChessSquare)i);
14228                     if( i >= gameInfo.holdingsSize ) return FALSE;
14229                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14230                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14231                 }
14232             }
14233         }
14234         if(*p == ']') *p++;
14235     }
14236
14237     while(*p == ' ') p++;
14238
14239     /* Active color */
14240     switch (*p++) {
14241       case 'w':
14242         *blackPlaysFirst = FALSE;
14243         break;
14244       case 'b': 
14245         *blackPlaysFirst = TRUE;
14246         break;
14247       default:
14248         return FALSE;
14249     }
14250
14251     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14252     /* return the extra info in global variiables             */
14253
14254     /* set defaults in case FEN is incomplete */
14255     board[EP_STATUS] = EP_UNKNOWN;
14256     for(i=0; i<nrCastlingRights; i++ ) {
14257         board[CASTLING][i] =
14258             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14259     }   /* assume possible unless obviously impossible */
14260     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14261     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14262     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14263                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14264     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14265     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14266     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14267                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14268     FENrulePlies = 0;
14269
14270     while(*p==' ') p++;
14271     if(nrCastlingRights) {
14272       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14273           /* castling indicator present, so default becomes no castlings */
14274           for(i=0; i<nrCastlingRights; i++ ) {
14275                  board[CASTLING][i] = NoRights;
14276           }
14277       }
14278       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14279              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14280              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14281              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14282         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14283
14284         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14285             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14286             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14287         }
14288         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14289             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14290         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14291                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14292         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14293                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14294         switch(c) {
14295           case'K':
14296               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14297               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14298               board[CASTLING][2] = whiteKingFile;
14299               break;
14300           case'Q':
14301               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14302               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14303               board[CASTLING][2] = whiteKingFile;
14304               break;
14305           case'k':
14306               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14307               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14308               board[CASTLING][5] = blackKingFile;
14309               break;
14310           case'q':
14311               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14312               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14313               board[CASTLING][5] = blackKingFile;
14314           case '-':
14315               break;
14316           default: /* FRC castlings */
14317               if(c >= 'a') { /* black rights */
14318                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14319                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14320                   if(i == BOARD_RGHT) break;
14321                   board[CASTLING][5] = i;
14322                   c -= AAA;
14323                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14324                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14325                   if(c > i)
14326                       board[CASTLING][3] = c;
14327                   else
14328                       board[CASTLING][4] = c;
14329               } else { /* white rights */
14330                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14331                     if(board[0][i] == WhiteKing) break;
14332                   if(i == BOARD_RGHT) break;
14333                   board[CASTLING][2] = i;
14334                   c -= AAA - 'a' + 'A';
14335                   if(board[0][c] >= WhiteKing) break;
14336                   if(c > i)
14337                       board[CASTLING][0] = c;
14338                   else
14339                       board[CASTLING][1] = c;
14340               }
14341         }
14342       }
14343       for(i=0; i<nrCastlingRights; i++)
14344         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14345     if (appData.debugMode) {
14346         fprintf(debugFP, "FEN castling rights:");
14347         for(i=0; i<nrCastlingRights; i++)
14348         fprintf(debugFP, " %d", board[CASTLING][i]);
14349         fprintf(debugFP, "\n");
14350     }
14351
14352       while(*p==' ') p++;
14353     }
14354
14355     /* read e.p. field in games that know e.p. capture */
14356     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14357        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14358       if(*p=='-') {
14359         p++; board[EP_STATUS] = EP_NONE;
14360       } else {
14361          char c = *p++ - AAA;
14362
14363          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14364          if(*p >= '0' && *p <='9') *p++;
14365          board[EP_STATUS] = c;
14366       }
14367     }
14368
14369
14370     if(sscanf(p, "%d", &i) == 1) {
14371         FENrulePlies = i; /* 50-move ply counter */
14372         /* (The move number is still ignored)    */
14373     }
14374
14375     return TRUE;
14376 }
14377       
14378 void
14379 EditPositionPasteFEN(char *fen)
14380 {
14381   if (fen != NULL) {
14382     Board initial_position;
14383
14384     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14385       DisplayError(_("Bad FEN position in clipboard"), 0);
14386       return ;
14387     } else {
14388       int savedBlackPlaysFirst = blackPlaysFirst;
14389       EditPositionEvent();
14390       blackPlaysFirst = savedBlackPlaysFirst;
14391       CopyBoard(boards[0], initial_position);
14392       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14393       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14394       DisplayBothClocks();
14395       DrawPosition(FALSE, boards[currentMove]);
14396     }
14397   }
14398 }
14399
14400 static char cseq[12] = "\\   ";
14401
14402 Boolean set_cont_sequence(char *new_seq)
14403 {
14404     int len;
14405     Boolean ret;
14406
14407     // handle bad attempts to set the sequence
14408         if (!new_seq)
14409                 return 0; // acceptable error - no debug
14410
14411     len = strlen(new_seq);
14412     ret = (len > 0) && (len < sizeof(cseq));
14413     if (ret)
14414         strcpy(cseq, new_seq);
14415     else if (appData.debugMode)
14416         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14417     return ret;
14418 }
14419
14420 /*
14421     reformat a source message so words don't cross the width boundary.  internal
14422     newlines are not removed.  returns the wrapped size (no null character unless
14423     included in source message).  If dest is NULL, only calculate the size required
14424     for the dest buffer.  lp argument indicats line position upon entry, and it's
14425     passed back upon exit.
14426 */
14427 int wrap(char *dest, char *src, int count, int width, int *lp)
14428 {
14429     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14430
14431     cseq_len = strlen(cseq);
14432     old_line = line = *lp;
14433     ansi = len = clen = 0;
14434
14435     for (i=0; i < count; i++)
14436     {
14437         if (src[i] == '\033')
14438             ansi = 1;
14439
14440         // if we hit the width, back up
14441         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14442         {
14443             // store i & len in case the word is too long
14444             old_i = i, old_len = len;
14445
14446             // find the end of the last word
14447             while (i && src[i] != ' ' && src[i] != '\n')
14448             {
14449                 i--;
14450                 len--;
14451             }
14452
14453             // word too long?  restore i & len before splitting it
14454             if ((old_i-i+clen) >= width)
14455             {
14456                 i = old_i;
14457                 len = old_len;
14458             }
14459
14460             // extra space?
14461             if (i && src[i-1] == ' ')
14462                 len--;
14463
14464             if (src[i] != ' ' && src[i] != '\n')
14465             {
14466                 i--;
14467                 if (len)
14468                     len--;
14469             }
14470
14471             // now append the newline and continuation sequence
14472             if (dest)
14473                 dest[len] = '\n';
14474             len++;
14475             if (dest)
14476                 strncpy(dest+len, cseq, cseq_len);
14477             len += cseq_len;
14478             line = cseq_len;
14479             clen = cseq_len;
14480             continue;
14481         }
14482
14483         if (dest)
14484             dest[len] = src[i];
14485         len++;
14486         if (!ansi)
14487             line++;
14488         if (src[i] == '\n')
14489             line = 0;
14490         if (src[i] == 'm')
14491             ansi = 0;
14492     }
14493     if (dest && appData.debugMode)
14494     {
14495         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14496             count, width, line, len, *lp);
14497         show_bytes(debugFP, src, count);
14498         fprintf(debugFP, "\ndest: ");
14499         show_bytes(debugFP, dest, len);
14500         fprintf(debugFP, "\n");
14501     }
14502     *lp = dest ? line : old_line;
14503
14504     return len;
14505 }
14506
14507 // [HGM] vari: routines for shelving variations
14508
14509 void 
14510 PushTail(int firstMove, int lastMove)
14511 {
14512         int i, j, nrMoves = lastMove - firstMove;
14513
14514         if(appData.icsActive) { // only in local mode
14515                 forwardMostMove = currentMove; // mimic old ICS behavior
14516                 return;
14517         }
14518         if(storedGames >= MAX_VARIATIONS-1) return;
14519
14520         // push current tail of game on stack
14521         savedResult[storedGames] = gameInfo.result;
14522         savedDetails[storedGames] = gameInfo.resultDetails;
14523         gameInfo.resultDetails = NULL;
14524         savedFirst[storedGames] = firstMove;
14525         savedLast [storedGames] = lastMove;
14526         savedFramePtr[storedGames] = framePtr;
14527         framePtr -= nrMoves; // reserve space for the boards
14528         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14529             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14530             for(j=0; j<MOVE_LEN; j++)
14531                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14532             for(j=0; j<2*MOVE_LEN; j++)
14533                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14534             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14535             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14536             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14537             pvInfoList[firstMove+i-1].depth = 0;
14538             commentList[framePtr+i] = commentList[firstMove+i];
14539             commentList[firstMove+i] = NULL;
14540         }
14541
14542         storedGames++;
14543         forwardMostMove = currentMove; // truncte game so we can start variation
14544         if(storedGames == 1) GreyRevert(FALSE);
14545 }
14546
14547 Boolean
14548 PopTail(Boolean annotate)
14549 {
14550         int i, j, nrMoves;
14551         char buf[8000], moveBuf[20];
14552
14553         if(appData.icsActive) return FALSE; // only in local mode
14554         if(!storedGames) return FALSE; // sanity
14555
14556         storedGames--;
14557         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14558         nrMoves = savedLast[storedGames] - currentMove;
14559         if(annotate) {
14560                 int cnt = 10;
14561                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14562                 else strcpy(buf, "(");
14563                 for(i=currentMove; i<forwardMostMove; i++) {
14564                         if(WhiteOnMove(i))
14565                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14566                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14567                         strcat(buf, moveBuf);
14568                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14569                 }
14570                 strcat(buf, ")");
14571         }
14572         for(i=1; i<nrMoves; i++) { // copy last variation back
14573             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14574             for(j=0; j<MOVE_LEN; j++)
14575                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14576             for(j=0; j<2*MOVE_LEN; j++)
14577                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14578             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14579             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14580             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14581             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14582             commentList[currentMove+i] = commentList[framePtr+i];
14583             commentList[framePtr+i] = NULL;
14584         }
14585         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14586         framePtr = savedFramePtr[storedGames];
14587         gameInfo.result = savedResult[storedGames];
14588         if(gameInfo.resultDetails != NULL) {
14589             free(gameInfo.resultDetails);
14590       }
14591         gameInfo.resultDetails = savedDetails[storedGames];
14592         forwardMostMove = currentMove + nrMoves;
14593         if(storedGames == 0) GreyRevert(TRUE);
14594         return TRUE;
14595 }
14596
14597 void 
14598 CleanupTail()
14599 {       // remove all shelved variations
14600         int i;
14601         for(i=0; i<storedGames; i++) {
14602             if(savedDetails[i])
14603                 free(savedDetails[i]);
14604             savedDetails[i] = NULL;
14605         }
14606         for(i=framePtr; i<MAX_MOVES; i++) {
14607                 if(commentList[i]) free(commentList[i]);
14608                 commentList[i] = NULL;
14609         }
14610         framePtr = MAX_MOVES-1;
14611         storedGames = 0;
14612 }