Shorten autoKibitz confirmation on FICS
[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                     char *p;
2476                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2477                     SendToPlayer(star_match[0], strlen(star_match[0]));
2478                     looking_at(buf, &i, "*% "); // eat prompt
2479                     next_out = i;
2480                 }
2481             } // [HGM] kibitz: end of patch
2482
2483 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2484
2485             // [HGM] chat: intercept tells by users for which we have an open chat window
2486             channel = -1;
2487             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2488                                            looking_at(buf, &i, "* whispers:") ||
2489                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2490                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2491                 int p;
2492                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2493                 chattingPartner = -1;
2494
2495                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2496                 for(p=0; p<MAX_CHAT; p++) {
2497                     if(channel == atoi(chatPartner[p])) {
2498                     talker[0] = '['; strcat(talker, "] ");
2499                     chattingPartner = p; break;
2500                     }
2501                 } else
2502                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2503                 for(p=0; p<MAX_CHAT; p++) {
2504                     if(!strcmp("WHISPER", chatPartner[p])) {
2505                         talker[0] = '['; strcat(talker, "] ");
2506                         chattingPartner = p; break;
2507                     }
2508                 }
2509                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2510                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2511                     talker[0] = 0;
2512                     chattingPartner = p; break;
2513                 }
2514                 if(chattingPartner<0) i = oldi; else {
2515                     started = STARTED_COMMENT;
2516                     parse_pos = 0; parse[0] = NULLCHAR;
2517                     savingComment = 3 + chattingPartner; // counts as TRUE
2518                     suppressKibitz = TRUE;
2519                 }
2520             } // [HGM] chat: end of patch
2521
2522             if (appData.zippyTalk || appData.zippyPlay) {
2523                 /* [DM] Backup address for color zippy lines */
2524                 backup = i;
2525 #if ZIPPY
2526        #ifdef WIN32
2527                if (loggedOn == TRUE)
2528                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2529                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2530        #else
2531                 if (ZippyControl(buf, &i) ||
2532                     ZippyConverse(buf, &i) ||
2533                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2534                       loggedOn = TRUE;
2535                       if (!appData.colorize) continue;
2536                 }
2537        #endif
2538 #endif
2539             } // [DM] 'else { ' deleted
2540                 if (
2541                     /* Regular tells and says */
2542                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2543                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2544                     looking_at(buf, &i, "* says: ") ||
2545                     /* Don't color "message" or "messages" output */
2546                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2547                     looking_at(buf, &i, "*. * at *:*: ") ||
2548                     looking_at(buf, &i, "--* (*:*): ") ||
2549                     /* Message notifications (same color as tells) */
2550                     looking_at(buf, &i, "* has left a message ") ||
2551                     looking_at(buf, &i, "* just sent you a message:\n") ||
2552                     /* Whispers and kibitzes */
2553                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2554                     looking_at(buf, &i, "* kibitzes: ") ||
2555                     /* Channel tells */
2556                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2557
2558                   if (tkind == 1 && strchr(star_match[0], ':')) {
2559                       /* Avoid "tells you:" spoofs in channels */
2560                      tkind = 3;
2561                   }
2562                   if (star_match[0][0] == NULLCHAR ||
2563                       strchr(star_match[0], ' ') ||
2564                       (tkind == 3 && strchr(star_match[1], ' '))) {
2565                     /* Reject bogus matches */
2566                     i = oldi;
2567                   } else {
2568                     if (appData.colorize) {
2569                       if (oldi > next_out) {
2570                         SendToPlayer(&buf[next_out], oldi - next_out);
2571                         next_out = oldi;
2572                       }
2573                       switch (tkind) {
2574                       case 1:
2575                         Colorize(ColorTell, FALSE);
2576                         curColor = ColorTell;
2577                         break;
2578                       case 2:
2579                         Colorize(ColorKibitz, FALSE);
2580                         curColor = ColorKibitz;
2581                         break;
2582                       case 3:
2583                         p = strrchr(star_match[1], '(');
2584                         if (p == NULL) {
2585                           p = star_match[1];
2586                         } else {
2587                           p++;
2588                         }
2589                         if (atoi(p) == 1) {
2590                           Colorize(ColorChannel1, FALSE);
2591                           curColor = ColorChannel1;
2592                         } else {
2593                           Colorize(ColorChannel, FALSE);
2594                           curColor = ColorChannel;
2595                         }
2596                         break;
2597                       case 5:
2598                         curColor = ColorNormal;
2599                         break;
2600                       }
2601                     }
2602                     if (started == STARTED_NONE && appData.autoComment &&
2603                         (gameMode == IcsObserving ||
2604                          gameMode == IcsPlayingWhite ||
2605                          gameMode == IcsPlayingBlack)) {
2606                       parse_pos = i - oldi;
2607                       memcpy(parse, &buf[oldi], parse_pos);
2608                       parse[parse_pos] = NULLCHAR;
2609                       started = STARTED_COMMENT;
2610                       savingComment = TRUE;
2611                     } else {
2612                       started = STARTED_CHATTER;
2613                       savingComment = FALSE;
2614                     }
2615                     loggedOn = TRUE;
2616                     continue;
2617                   }
2618                 }
2619
2620                 if (looking_at(buf, &i, "* s-shouts: ") ||
2621                     looking_at(buf, &i, "* c-shouts: ")) {
2622                     if (appData.colorize) {
2623                         if (oldi > next_out) {
2624                             SendToPlayer(&buf[next_out], oldi - next_out);
2625                             next_out = oldi;
2626                         }
2627                         Colorize(ColorSShout, FALSE);
2628                         curColor = ColorSShout;
2629                     }
2630                     loggedOn = TRUE;
2631                     started = STARTED_CHATTER;
2632                     continue;
2633                 }
2634
2635                 if (looking_at(buf, &i, "--->")) {
2636                     loggedOn = TRUE;
2637                     continue;
2638                 }
2639
2640                 if (looking_at(buf, &i, "* shouts: ") ||
2641                     looking_at(buf, &i, "--> ")) {
2642                     if (appData.colorize) {
2643                         if (oldi > next_out) {
2644                             SendToPlayer(&buf[next_out], oldi - next_out);
2645                             next_out = oldi;
2646                         }
2647                         Colorize(ColorShout, FALSE);
2648                         curColor = ColorShout;
2649                     }
2650                     loggedOn = TRUE;
2651                     started = STARTED_CHATTER;
2652                     continue;
2653                 }
2654
2655                 if (looking_at( buf, &i, "Challenge:")) {
2656                     if (appData.colorize) {
2657                         if (oldi > next_out) {
2658                             SendToPlayer(&buf[next_out], oldi - next_out);
2659                             next_out = oldi;
2660                         }
2661                         Colorize(ColorChallenge, FALSE);
2662                         curColor = ColorChallenge;
2663                     }
2664                     loggedOn = TRUE;
2665                     continue;
2666                 }
2667
2668                 if (looking_at(buf, &i, "* offers you") ||
2669                     looking_at(buf, &i, "* offers to be") ||
2670                     looking_at(buf, &i, "* would like to") ||
2671                     looking_at(buf, &i, "* requests to") ||
2672                     looking_at(buf, &i, "Your opponent offers") ||
2673                     looking_at(buf, &i, "Your opponent requests")) {
2674
2675                     if (appData.colorize) {
2676                         if (oldi > next_out) {
2677                             SendToPlayer(&buf[next_out], oldi - next_out);
2678                             next_out = oldi;
2679                         }
2680                         Colorize(ColorRequest, FALSE);
2681                         curColor = ColorRequest;
2682                     }
2683                     continue;
2684                 }
2685
2686                 if (looking_at(buf, &i, "* (*) seeking")) {
2687                     if (appData.colorize) {
2688                         if (oldi > next_out) {
2689                             SendToPlayer(&buf[next_out], oldi - next_out);
2690                             next_out = oldi;
2691                         }
2692                         Colorize(ColorSeek, FALSE);
2693                         curColor = ColorSeek;
2694                     }
2695                     continue;
2696             }
2697
2698             if (looking_at(buf, &i, "\\   ")) {
2699                 if (prevColor != ColorNormal) {
2700                     if (oldi > next_out) {
2701                         SendToPlayer(&buf[next_out], oldi - next_out);
2702                         next_out = oldi;
2703                     }
2704                     Colorize(prevColor, TRUE);
2705                     curColor = prevColor;
2706                 }
2707                 if (savingComment) {
2708                     parse_pos = i - oldi;
2709                     memcpy(parse, &buf[oldi], parse_pos);
2710                     parse[parse_pos] = NULLCHAR;
2711                     started = STARTED_COMMENT;
2712                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2713                         chattingPartner = savingComment - 3; // kludge to remember the box
2714                 } else {
2715                     started = STARTED_CHATTER;
2716                 }
2717                 continue;
2718             }
2719
2720             if (looking_at(buf, &i, "Black Strength :") ||
2721                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2722                 looking_at(buf, &i, "<10>") ||
2723                 looking_at(buf, &i, "#@#")) {
2724                 /* Wrong board style */
2725                 loggedOn = TRUE;
2726                 SendToICS(ics_prefix);
2727                 SendToICS("set style 12\n");
2728                 SendToICS(ics_prefix);
2729                 SendToICS("refresh\n");
2730                 continue;
2731             }
2732             
2733             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2734                 ICSInitScript();
2735                 have_sent_ICS_logon = 1;
2736                 continue;
2737             }
2738               
2739             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2740                 (looking_at(buf, &i, "\n<12> ") ||
2741                  looking_at(buf, &i, "<12> "))) {
2742                 loggedOn = TRUE;
2743                 if (oldi > next_out) {
2744                     SendToPlayer(&buf[next_out], oldi - next_out);
2745                 }
2746                 next_out = i;
2747                 started = STARTED_BOARD;
2748                 parse_pos = 0;
2749                 continue;
2750             }
2751
2752             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2753                 looking_at(buf, &i, "<b1> ")) {
2754                 if (oldi > next_out) {
2755                     SendToPlayer(&buf[next_out], oldi - next_out);
2756                 }
2757                 next_out = i;
2758                 started = STARTED_HOLDINGS;
2759                 parse_pos = 0;
2760                 continue;
2761             }
2762
2763             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2764                 loggedOn = TRUE;
2765                 /* Header for a move list -- first line */
2766
2767                 switch (ics_getting_history) {
2768                   case H_FALSE:
2769                     switch (gameMode) {
2770                       case IcsIdle:
2771                       case BeginningOfGame:
2772                         /* User typed "moves" or "oldmoves" while we
2773                            were idle.  Pretend we asked for these
2774                            moves and soak them up so user can step
2775                            through them and/or save them.
2776                            */
2777                         Reset(FALSE, TRUE);
2778                         gameMode = IcsObserving;
2779                         ModeHighlight();
2780                         ics_gamenum = -1;
2781                         ics_getting_history = H_GOT_UNREQ_HEADER;
2782                         break;
2783                       case EditGame: /*?*/
2784                       case EditPosition: /*?*/
2785                         /* Should above feature work in these modes too? */
2786                         /* For now it doesn't */
2787                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2788                         break;
2789                       default:
2790                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2791                         break;
2792                     }
2793                     break;
2794                   case H_REQUESTED:
2795                     /* Is this the right one? */
2796                     if (gameInfo.white && gameInfo.black &&
2797                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2798                         strcmp(gameInfo.black, star_match[2]) == 0) {
2799                         /* All is well */
2800                         ics_getting_history = H_GOT_REQ_HEADER;
2801                     }
2802                     break;
2803                   case H_GOT_REQ_HEADER:
2804                   case H_GOT_UNREQ_HEADER:
2805                   case H_GOT_UNWANTED_HEADER:
2806                   case H_GETTING_MOVES:
2807                     /* Should not happen */
2808                     DisplayError(_("Error gathering move list: two headers"), 0);
2809                     ics_getting_history = H_FALSE;
2810                     break;
2811                 }
2812
2813                 /* Save player ratings into gameInfo if needed */
2814                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2815                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2816                     (gameInfo.whiteRating == -1 ||
2817                      gameInfo.blackRating == -1)) {
2818
2819                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2820                     gameInfo.blackRating = string_to_rating(star_match[3]);
2821                     if (appData.debugMode)
2822                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2823                               gameInfo.whiteRating, gameInfo.blackRating);
2824                 }
2825                 continue;
2826             }
2827
2828             if (looking_at(buf, &i,
2829               "* * match, initial time: * minute*, increment: * second")) {
2830                 /* Header for a move list -- second line */
2831                 /* Initial board will follow if this is a wild game */
2832                 if (gameInfo.event != NULL) free(gameInfo.event);
2833                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2834                 gameInfo.event = StrSave(str);
2835                 /* [HGM] we switched variant. Translate boards if needed. */
2836                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2837                 continue;
2838             }
2839
2840             if (looking_at(buf, &i, "Move  ")) {
2841                 /* Beginning of a move list */
2842                 switch (ics_getting_history) {
2843                   case H_FALSE:
2844                     /* Normally should not happen */
2845                     /* Maybe user hit reset while we were parsing */
2846                     break;
2847                   case H_REQUESTED:
2848                     /* Happens if we are ignoring a move list that is not
2849                      * the one we just requested.  Common if the user
2850                      * tries to observe two games without turning off
2851                      * getMoveList */
2852                     break;
2853                   case H_GETTING_MOVES:
2854                     /* Should not happen */
2855                     DisplayError(_("Error gathering move list: nested"), 0);
2856                     ics_getting_history = H_FALSE;
2857                     break;
2858                   case H_GOT_REQ_HEADER:
2859                     ics_getting_history = H_GETTING_MOVES;
2860                     started = STARTED_MOVES;
2861                     parse_pos = 0;
2862                     if (oldi > next_out) {
2863                         SendToPlayer(&buf[next_out], oldi - next_out);
2864                     }
2865                     break;
2866                   case H_GOT_UNREQ_HEADER:
2867                     ics_getting_history = H_GETTING_MOVES;
2868                     started = STARTED_MOVES_NOHIDE;
2869                     parse_pos = 0;
2870                     break;
2871                   case H_GOT_UNWANTED_HEADER:
2872                     ics_getting_history = H_FALSE;
2873                     break;
2874                 }
2875                 continue;
2876             }                           
2877             
2878             if (looking_at(buf, &i, "% ") ||
2879                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2880                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2881                 if(suppressKibitz) next_out = i;
2882                 savingComment = FALSE;
2883                 suppressKibitz = 0;
2884                 switch (started) {
2885                   case STARTED_MOVES:
2886                   case STARTED_MOVES_NOHIDE:
2887                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2888                     parse[parse_pos + i - oldi] = NULLCHAR;
2889                     ParseGameHistory(parse);
2890 #if ZIPPY
2891                     if (appData.zippyPlay && first.initDone) {
2892                         FeedMovesToProgram(&first, forwardMostMove);
2893                         if (gameMode == IcsPlayingWhite) {
2894                             if (WhiteOnMove(forwardMostMove)) {
2895                                 if (first.sendTime) {
2896                                   if (first.useColors) {
2897                                     SendToProgram("black\n", &first); 
2898                                   }
2899                                   SendTimeRemaining(&first, TRUE);
2900                                 }
2901                                 if (first.useColors) {
2902                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2903                                 }
2904                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2905                                 first.maybeThinking = TRUE;
2906                             } else {
2907                                 if (first.usePlayother) {
2908                                   if (first.sendTime) {
2909                                     SendTimeRemaining(&first, TRUE);
2910                                   }
2911                                   SendToProgram("playother\n", &first);
2912                                   firstMove = FALSE;
2913                                 } else {
2914                                   firstMove = TRUE;
2915                                 }
2916                             }
2917                         } else if (gameMode == IcsPlayingBlack) {
2918                             if (!WhiteOnMove(forwardMostMove)) {
2919                                 if (first.sendTime) {
2920                                   if (first.useColors) {
2921                                     SendToProgram("white\n", &first);
2922                                   }
2923                                   SendTimeRemaining(&first, FALSE);
2924                                 }
2925                                 if (first.useColors) {
2926                                   SendToProgram("black\n", &first);
2927                                 }
2928                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2929                                 first.maybeThinking = TRUE;
2930                             } else {
2931                                 if (first.usePlayother) {
2932                                   if (first.sendTime) {
2933                                     SendTimeRemaining(&first, FALSE);
2934                                   }
2935                                   SendToProgram("playother\n", &first);
2936                                   firstMove = FALSE;
2937                                 } else {
2938                                   firstMove = TRUE;
2939                                 }
2940                             }
2941                         }                       
2942                     }
2943 #endif
2944                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2945                         /* Moves came from oldmoves or moves command
2946                            while we weren't doing anything else.
2947                            */
2948                         currentMove = forwardMostMove;
2949                         ClearHighlights();/*!!could figure this out*/
2950                         flipView = appData.flipView;
2951                         DrawPosition(TRUE, boards[currentMove]);
2952                         DisplayBothClocks();
2953                         sprintf(str, "%s vs. %s",
2954                                 gameInfo.white, gameInfo.black);
2955                         DisplayTitle(str);
2956                         gameMode = IcsIdle;
2957                     } else {
2958                         /* Moves were history of an active game */
2959                         if (gameInfo.resultDetails != NULL) {
2960                             free(gameInfo.resultDetails);
2961                             gameInfo.resultDetails = NULL;
2962                         }
2963                     }
2964                     HistorySet(parseList, backwardMostMove,
2965                                forwardMostMove, currentMove-1);
2966                     DisplayMove(currentMove - 1);
2967                     if (started == STARTED_MOVES) next_out = i;
2968                     started = STARTED_NONE;
2969                     ics_getting_history = H_FALSE;
2970                     break;
2971
2972                   case STARTED_OBSERVE:
2973                     started = STARTED_NONE;
2974                     SendToICS(ics_prefix);
2975                     SendToICS("refresh\n");
2976                     break;
2977
2978                   default:
2979                     break;
2980                 }
2981                 if(bookHit) { // [HGM] book: simulate book reply
2982                     static char bookMove[MSG_SIZ]; // a bit generous?
2983
2984                     programStats.nodes = programStats.depth = programStats.time = 
2985                     programStats.score = programStats.got_only_move = 0;
2986                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2987
2988                     strcpy(bookMove, "move ");
2989                     strcat(bookMove, bookHit);
2990                     HandleMachineMove(bookMove, &first);
2991                 }
2992                 continue;
2993             }
2994             
2995             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2996                  started == STARTED_HOLDINGS ||
2997                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2998                 /* Accumulate characters in move list or board */
2999                 parse[parse_pos++] = buf[i];
3000             }
3001             
3002             /* Start of game messages.  Mostly we detect start of game
3003                when the first board image arrives.  On some versions
3004                of the ICS, though, we need to do a "refresh" after starting
3005                to observe in order to get the current board right away. */
3006             if (looking_at(buf, &i, "Adding game * to observation list")) {
3007                 started = STARTED_OBSERVE;
3008                 continue;
3009             }
3010
3011             /* Handle auto-observe */
3012             if (appData.autoObserve &&
3013                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3014                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3015                 char *player;
3016                 /* Choose the player that was highlighted, if any. */
3017                 if (star_match[0][0] == '\033' ||
3018                     star_match[1][0] != '\033') {
3019                     player = star_match[0];
3020                 } else {
3021                     player = star_match[2];
3022                 }
3023                 sprintf(str, "%sobserve %s\n",
3024                         ics_prefix, StripHighlightAndTitle(player));
3025                 SendToICS(str);
3026
3027                 /* Save ratings from notify string */
3028                 strcpy(player1Name, star_match[0]);
3029                 player1Rating = string_to_rating(star_match[1]);
3030                 strcpy(player2Name, star_match[2]);
3031                 player2Rating = string_to_rating(star_match[3]);
3032
3033                 if (appData.debugMode)
3034                   fprintf(debugFP, 
3035                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3036                           player1Name, player1Rating,
3037                           player2Name, player2Rating);
3038
3039                 continue;
3040             }
3041
3042             /* Deal with automatic examine mode after a game,
3043                and with IcsObserving -> IcsExamining transition */
3044             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3045                 looking_at(buf, &i, "has made you an examiner of game *")) {
3046
3047                 int gamenum = atoi(star_match[0]);
3048                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3049                     gamenum == ics_gamenum) {
3050                     /* We were already playing or observing this game;
3051                        no need to refetch history */
3052                     gameMode = IcsExamining;
3053                     if (pausing) {
3054                         pauseExamForwardMostMove = forwardMostMove;
3055                     } else if (currentMove < forwardMostMove) {
3056                         ForwardInner(forwardMostMove);
3057                     }
3058                 } else {
3059                     /* I don't think this case really can happen */
3060                     SendToICS(ics_prefix);
3061                     SendToICS("refresh\n");
3062                 }
3063                 continue;
3064             }    
3065             
3066             /* Error messages */
3067 //          if (ics_user_moved) {
3068             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3069                 if (looking_at(buf, &i, "Illegal move") ||
3070                     looking_at(buf, &i, "Not a legal move") ||
3071                     looking_at(buf, &i, "Your king is in check") ||
3072                     looking_at(buf, &i, "It isn't your turn") ||
3073                     looking_at(buf, &i, "It is not your move")) {
3074                     /* Illegal move */
3075                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3076                         currentMove = --forwardMostMove;
3077                         DisplayMove(currentMove - 1); /* before DMError */
3078                         DrawPosition(FALSE, boards[currentMove]);
3079                         SwitchClocks();
3080                         DisplayBothClocks();
3081                     }
3082                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3083                     ics_user_moved = 0;
3084                     continue;
3085                 }
3086             }
3087
3088             if (looking_at(buf, &i, "still have time") ||
3089                 looking_at(buf, &i, "not out of time") ||
3090                 looking_at(buf, &i, "either player is out of time") ||
3091                 looking_at(buf, &i, "has timeseal; checking")) {
3092                 /* We must have called his flag a little too soon */
3093                 whiteFlag = blackFlag = FALSE;
3094                 continue;
3095             }
3096
3097             if (looking_at(buf, &i, "added * seconds to") ||
3098                 looking_at(buf, &i, "seconds were added to")) {
3099                 /* Update the clocks */
3100                 SendToICS(ics_prefix);
3101                 SendToICS("refresh\n");
3102                 continue;
3103             }
3104
3105             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3106                 ics_clock_paused = TRUE;
3107                 StopClocks();
3108                 continue;
3109             }
3110
3111             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3112                 ics_clock_paused = FALSE;
3113                 StartClocks();
3114                 continue;
3115             }
3116
3117             /* Grab player ratings from the Creating: message.
3118                Note we have to check for the special case when
3119                the ICS inserts things like [white] or [black]. */
3120             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3121                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3122                 /* star_matches:
3123                    0    player 1 name (not necessarily white)
3124                    1    player 1 rating
3125                    2    empty, white, or black (IGNORED)
3126                    3    player 2 name (not necessarily black)
3127                    4    player 2 rating
3128                    
3129                    The names/ratings are sorted out when the game
3130                    actually starts (below).
3131                 */
3132                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3133                 player1Rating = string_to_rating(star_match[1]);
3134                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3135                 player2Rating = string_to_rating(star_match[4]);
3136
3137                 if (appData.debugMode)
3138                   fprintf(debugFP, 
3139                           "Ratings from 'Creating:' %s %d, %s %d\n",
3140                           player1Name, player1Rating,
3141                           player2Name, player2Rating);
3142
3143                 continue;
3144             }
3145             
3146             /* Improved generic start/end-of-game messages */
3147             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3148                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3149                 /* If tkind == 0: */
3150                 /* star_match[0] is the game number */
3151                 /*           [1] is the white player's name */
3152                 /*           [2] is the black player's name */
3153                 /* For end-of-game: */
3154                 /*           [3] is the reason for the game end */
3155                 /*           [4] is a PGN end game-token, preceded by " " */
3156                 /* For start-of-game: */
3157                 /*           [3] begins with "Creating" or "Continuing" */
3158                 /*           [4] is " *" or empty (don't care). */
3159                 int gamenum = atoi(star_match[0]);
3160                 char *whitename, *blackname, *why, *endtoken;
3161                 ChessMove endtype = (ChessMove) 0;
3162
3163                 if (tkind == 0) {
3164                   whitename = star_match[1];
3165                   blackname = star_match[2];
3166                   why = star_match[3];
3167                   endtoken = star_match[4];
3168                 } else {
3169                   whitename = star_match[1];
3170                   blackname = star_match[3];
3171                   why = star_match[5];
3172                   endtoken = star_match[6];
3173                 }
3174
3175                 /* Game start messages */
3176                 if (strncmp(why, "Creating ", 9) == 0 ||
3177                     strncmp(why, "Continuing ", 11) == 0) {
3178                     gs_gamenum = gamenum;
3179                     strcpy(gs_kind, strchr(why, ' ') + 1);
3180                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3181 #if ZIPPY
3182                     if (appData.zippyPlay) {
3183                         ZippyGameStart(whitename, blackname);
3184                     }
3185 #endif /*ZIPPY*/
3186                     continue;
3187                 }
3188
3189                 /* Game end messages */
3190                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3191                     ics_gamenum != gamenum) {
3192                     continue;
3193                 }
3194                 while (endtoken[0] == ' ') endtoken++;
3195                 switch (endtoken[0]) {
3196                   case '*':
3197                   default:
3198                     endtype = GameUnfinished;
3199                     break;
3200                   case '0':
3201                     endtype = BlackWins;
3202                     break;
3203                   case '1':
3204                     if (endtoken[1] == '/')
3205                       endtype = GameIsDrawn;
3206                     else
3207                       endtype = WhiteWins;
3208                     break;
3209                 }
3210                 GameEnds(endtype, why, GE_ICS);
3211 #if ZIPPY
3212                 if (appData.zippyPlay && first.initDone) {
3213                     ZippyGameEnd(endtype, why);
3214                     if (first.pr == NULL) {
3215                       /* Start the next process early so that we'll
3216                          be ready for the next challenge */
3217                       StartChessProgram(&first);
3218                     }
3219                     /* Send "new" early, in case this command takes
3220                        a long time to finish, so that we'll be ready
3221                        for the next challenge. */
3222                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3223                     Reset(TRUE, TRUE);
3224                 }
3225 #endif /*ZIPPY*/
3226                 continue;
3227             }
3228
3229             if (looking_at(buf, &i, "Removing game * from observation") ||
3230                 looking_at(buf, &i, "no longer observing game *") ||
3231                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3232                 if (gameMode == IcsObserving &&
3233                     atoi(star_match[0]) == ics_gamenum)
3234                   {
3235                       /* icsEngineAnalyze */
3236                       if (appData.icsEngineAnalyze) {
3237                             ExitAnalyzeMode();
3238                             ModeHighlight();
3239                       }
3240                       StopClocks();
3241                       gameMode = IcsIdle;
3242                       ics_gamenum = -1;
3243                       ics_user_moved = FALSE;
3244                   }
3245                 continue;
3246             }
3247
3248             if (looking_at(buf, &i, "no longer examining game *")) {
3249                 if (gameMode == IcsExamining &&
3250                     atoi(star_match[0]) == ics_gamenum)
3251                   {
3252                       gameMode = IcsIdle;
3253                       ics_gamenum = -1;
3254                       ics_user_moved = FALSE;
3255                   }
3256                 continue;
3257             }
3258
3259             /* Advance leftover_start past any newlines we find,
3260                so only partial lines can get reparsed */
3261             if (looking_at(buf, &i, "\n")) {
3262                 prevColor = curColor;
3263                 if (curColor != ColorNormal) {
3264                     if (oldi > next_out) {
3265                         SendToPlayer(&buf[next_out], oldi - next_out);
3266                         next_out = oldi;
3267                     }
3268                     Colorize(ColorNormal, FALSE);
3269                     curColor = ColorNormal;
3270                 }
3271                 if (started == STARTED_BOARD) {
3272                     started = STARTED_NONE;
3273                     parse[parse_pos] = NULLCHAR;
3274                     ParseBoard12(parse);
3275                     ics_user_moved = 0;
3276
3277                     /* Send premove here */
3278                     if (appData.premove) {
3279                       char str[MSG_SIZ];
3280                       if (currentMove == 0 &&
3281                           gameMode == IcsPlayingWhite &&
3282                           appData.premoveWhite) {
3283                         sprintf(str, "%s\n", appData.premoveWhiteText);
3284                         if (appData.debugMode)
3285                           fprintf(debugFP, "Sending premove:\n");
3286                         SendToICS(str);
3287                       } else if (currentMove == 1 &&
3288                                  gameMode == IcsPlayingBlack &&
3289                                  appData.premoveBlack) {
3290                         sprintf(str, "%s\n", appData.premoveBlackText);
3291                         if (appData.debugMode)
3292                           fprintf(debugFP, "Sending premove:\n");
3293                         SendToICS(str);
3294                       } else if (gotPremove) {
3295                         gotPremove = 0;
3296                         ClearPremoveHighlights();
3297                         if (appData.debugMode)
3298                           fprintf(debugFP, "Sending premove:\n");
3299                           UserMoveEvent(premoveFromX, premoveFromY, 
3300                                         premoveToX, premoveToY, 
3301                                         premovePromoChar);
3302                       }
3303                     }
3304
3305                     /* Usually suppress following prompt */
3306                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3307                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3308                         if (looking_at(buf, &i, "*% ")) {
3309                             savingComment = FALSE;
3310                             suppressKibitz = 0;
3311                         }
3312                     }
3313                     next_out = i;
3314                 } else if (started == STARTED_HOLDINGS) {
3315                     int gamenum;
3316                     char new_piece[MSG_SIZ];
3317                     started = STARTED_NONE;
3318                     parse[parse_pos] = NULLCHAR;
3319                     if (appData.debugMode)
3320                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3321                                                         parse, currentMove);
3322                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3323                         gamenum == ics_gamenum) {
3324                         if (gameInfo.variant == VariantNormal) {
3325                           /* [HGM] We seem to switch variant during a game!
3326                            * Presumably no holdings were displayed, so we have
3327                            * to move the position two files to the right to
3328                            * create room for them!
3329                            */
3330                           VariantClass newVariant;
3331                           switch(gameInfo.boardWidth) { // base guess on board width
3332                                 case 9:  newVariant = VariantShogi; break;
3333                                 case 10: newVariant = VariantGreat; break;
3334                                 default: newVariant = VariantCrazyhouse; break;
3335                           }
3336                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3337                           /* Get a move list just to see the header, which
3338                              will tell us whether this is really bug or zh */
3339                           if (ics_getting_history == H_FALSE) {
3340                             ics_getting_history = H_REQUESTED;
3341                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3342                             SendToICS(str);
3343                           }
3344                         }
3345                         new_piece[0] = NULLCHAR;
3346                         sscanf(parse, "game %d white [%s black [%s <- %s",
3347                                &gamenum, white_holding, black_holding,
3348                                new_piece);
3349                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3350                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3351                         /* [HGM] copy holdings to board holdings area */
3352                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3353                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3354                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3355 #if ZIPPY
3356                         if (appData.zippyPlay && first.initDone) {
3357                             ZippyHoldings(white_holding, black_holding,
3358                                           new_piece);
3359                         }
3360 #endif /*ZIPPY*/
3361                         if (tinyLayout || smallLayout) {
3362                             char wh[16], bh[16];
3363                             PackHolding(wh, white_holding);
3364                             PackHolding(bh, black_holding);
3365                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3366                                     gameInfo.white, gameInfo.black);
3367                         } else {
3368                             sprintf(str, "%s [%s] vs. %s [%s]",
3369                                     gameInfo.white, white_holding,
3370                                     gameInfo.black, black_holding);
3371                         }
3372
3373                         DrawPosition(FALSE, boards[currentMove]);
3374                         DisplayTitle(str);
3375                     }
3376                     /* Suppress following prompt */
3377                     if (looking_at(buf, &i, "*% ")) {
3378                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3379                         savingComment = FALSE;
3380                         suppressKibitz = 0;
3381                     }
3382                     next_out = i;
3383                 }
3384                 continue;
3385             }
3386
3387             i++;                /* skip unparsed character and loop back */
3388         }
3389         
3390         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3391 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3392 //          SendToPlayer(&buf[next_out], i - next_out);
3393             started != STARTED_HOLDINGS && leftover_start > next_out) {
3394             SendToPlayer(&buf[next_out], leftover_start - next_out);
3395             next_out = i;
3396         }
3397         
3398         leftover_len = buf_len - leftover_start;
3399         /* if buffer ends with something we couldn't parse,
3400            reparse it after appending the next read */
3401         
3402     } else if (count == 0) {
3403         RemoveInputSource(isr);
3404         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3405     } else {
3406         DisplayFatalError(_("Error reading from ICS"), error, 1);
3407     }
3408 }
3409
3410
3411 /* Board style 12 looks like this:
3412    
3413    <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
3414    
3415  * The "<12> " is stripped before it gets to this routine.  The two
3416  * trailing 0's (flip state and clock ticking) are later addition, and
3417  * some chess servers may not have them, or may have only the first.
3418  * Additional trailing fields may be added in the future.  
3419  */
3420
3421 #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"
3422
3423 #define RELATION_OBSERVING_PLAYED    0
3424 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3425 #define RELATION_PLAYING_MYMOVE      1
3426 #define RELATION_PLAYING_NOTMYMOVE  -1
3427 #define RELATION_EXAMINING           2
3428 #define RELATION_ISOLATED_BOARD     -3
3429 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3430
3431 void
3432 ParseBoard12(string)
3433      char *string;
3434
3435     GameMode newGameMode;
3436     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3437     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3438     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3439     char to_play, board_chars[200];
3440     char move_str[500], str[500], elapsed_time[500];
3441     char black[32], white[32];
3442     Board board;
3443     int prevMove = currentMove;
3444     int ticking = 2;
3445     ChessMove moveType;
3446     int fromX, fromY, toX, toY;
3447     char promoChar;
3448     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3449     char *bookHit = NULL; // [HGM] book
3450     Boolean weird = FALSE, reqFlag = FALSE;
3451
3452     fromX = fromY = toX = toY = -1;
3453     
3454     newGame = FALSE;
3455
3456     if (appData.debugMode)
3457       fprintf(debugFP, _("Parsing board: %s\n"), string);
3458
3459     move_str[0] = NULLCHAR;
3460     elapsed_time[0] = NULLCHAR;
3461     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3462         int  i = 0, j;
3463         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3464             if(string[i] == ' ') { ranks++; files = 0; }
3465             else files++;
3466             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3467             i++;
3468         }
3469         for(j = 0; j <i; j++) board_chars[j] = string[j];
3470         board_chars[i] = '\0';
3471         string += i + 1;
3472     }
3473     n = sscanf(string, PATTERN, &to_play, &double_push,
3474                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3475                &gamenum, white, black, &relation, &basetime, &increment,
3476                &white_stren, &black_stren, &white_time, &black_time,
3477                &moveNum, str, elapsed_time, move_str, &ics_flip,
3478                &ticking);
3479
3480     if (n < 21) {
3481         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3482         DisplayError(str, 0);
3483         return;
3484     }
3485
3486     /* Convert the move number to internal form */
3487     moveNum = (moveNum - 1) * 2;
3488     if (to_play == 'B') moveNum++;
3489     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3490       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3491                         0, 1);
3492       return;
3493     }
3494     
3495     switch (relation) {
3496       case RELATION_OBSERVING_PLAYED:
3497       case RELATION_OBSERVING_STATIC:
3498         if (gamenum == -1) {
3499             /* Old ICC buglet */
3500             relation = RELATION_OBSERVING_STATIC;
3501         }
3502         newGameMode = IcsObserving;
3503         break;
3504       case RELATION_PLAYING_MYMOVE:
3505       case RELATION_PLAYING_NOTMYMOVE:
3506         newGameMode =
3507           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3508             IcsPlayingWhite : IcsPlayingBlack;
3509         break;
3510       case RELATION_EXAMINING:
3511         newGameMode = IcsExamining;
3512         break;
3513       case RELATION_ISOLATED_BOARD:
3514       default:
3515         /* Just display this board.  If user was doing something else,
3516            we will forget about it until the next board comes. */ 
3517         newGameMode = IcsIdle;
3518         break;
3519       case RELATION_STARTING_POSITION:
3520         newGameMode = gameMode;
3521         break;
3522     }
3523     
3524     /* Modify behavior for initial board display on move listing
3525        of wild games.
3526        */
3527     switch (ics_getting_history) {
3528       case H_FALSE:
3529       case H_REQUESTED:
3530         break;
3531       case H_GOT_REQ_HEADER:
3532       case H_GOT_UNREQ_HEADER:
3533         /* This is the initial position of the current game */
3534         gamenum = ics_gamenum;
3535         moveNum = 0;            /* old ICS bug workaround */
3536         if (to_play == 'B') {
3537           startedFromSetupPosition = TRUE;
3538           blackPlaysFirst = TRUE;
3539           moveNum = 1;
3540           if (forwardMostMove == 0) forwardMostMove = 1;
3541           if (backwardMostMove == 0) backwardMostMove = 1;
3542           if (currentMove == 0) currentMove = 1;
3543         }
3544         newGameMode = gameMode;
3545         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3546         break;
3547       case H_GOT_UNWANTED_HEADER:
3548         /* This is an initial board that we don't want */
3549         return;
3550       case H_GETTING_MOVES:
3551         /* Should not happen */
3552         DisplayError(_("Error gathering move list: extra board"), 0);
3553         ics_getting_history = H_FALSE;
3554         return;
3555     }
3556
3557    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3558                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3559      /* [HGM] We seem to have switched variant unexpectedly
3560       * Try to guess new variant from board size
3561       */
3562           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3563           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3564           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3565           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3566           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3567           if(!weird) newVariant = VariantNormal;
3568           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3569           /* Get a move list just to see the header, which
3570              will tell us whether this is really bug or zh */
3571           if (ics_getting_history == H_FALSE) {
3572             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3573             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3574             SendToICS(str);
3575           }
3576     }
3577     
3578     /* Take action if this is the first board of a new game, or of a
3579        different game than is currently being displayed.  */
3580     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3581         relation == RELATION_ISOLATED_BOARD) {
3582         
3583         /* Forget the old game and get the history (if any) of the new one */
3584         if (gameMode != BeginningOfGame) {
3585           Reset(TRUE, TRUE);
3586         }
3587         newGame = TRUE;
3588         if (appData.autoRaiseBoard) BoardToTop();
3589         prevMove = -3;
3590         if (gamenum == -1) {
3591             newGameMode = IcsIdle;
3592         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3593                    appData.getMoveList && !reqFlag) {
3594             /* Need to get game history */
3595             ics_getting_history = H_REQUESTED;
3596             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3597             SendToICS(str);
3598         }
3599         
3600         /* Initially flip the board to have black on the bottom if playing
3601            black or if the ICS flip flag is set, but let the user change
3602            it with the Flip View button. */
3603         flipView = appData.autoFlipView ? 
3604           (newGameMode == IcsPlayingBlack) || ics_flip :
3605           appData.flipView;
3606         
3607         /* Done with values from previous mode; copy in new ones */
3608         gameMode = newGameMode;
3609         ModeHighlight();
3610         ics_gamenum = gamenum;
3611         if (gamenum == gs_gamenum) {
3612             int klen = strlen(gs_kind);
3613             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3614             sprintf(str, "ICS %s", gs_kind);
3615             gameInfo.event = StrSave(str);
3616         } else {
3617             gameInfo.event = StrSave("ICS game");
3618         }
3619         gameInfo.site = StrSave(appData.icsHost);
3620         gameInfo.date = PGNDate();
3621         gameInfo.round = StrSave("-");
3622         gameInfo.white = StrSave(white);
3623         gameInfo.black = StrSave(black);
3624         timeControl = basetime * 60 * 1000;
3625         timeControl_2 = 0;
3626         timeIncrement = increment * 1000;
3627         movesPerSession = 0;
3628         gameInfo.timeControl = TimeControlTagValue();
3629         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3630   if (appData.debugMode) {
3631     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3632     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3633     setbuf(debugFP, NULL);
3634   }
3635
3636         gameInfo.outOfBook = NULL;
3637         
3638         /* Do we have the ratings? */
3639         if (strcmp(player1Name, white) == 0 &&
3640             strcmp(player2Name, black) == 0) {
3641             if (appData.debugMode)
3642               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3643                       player1Rating, player2Rating);
3644             gameInfo.whiteRating = player1Rating;
3645             gameInfo.blackRating = player2Rating;
3646         } else if (strcmp(player2Name, white) == 0 &&
3647                    strcmp(player1Name, black) == 0) {
3648             if (appData.debugMode)
3649               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3650                       player2Rating, player1Rating);
3651             gameInfo.whiteRating = player2Rating;
3652             gameInfo.blackRating = player1Rating;
3653         }
3654         player1Name[0] = player2Name[0] = NULLCHAR;
3655
3656         /* Silence shouts if requested */
3657         if (appData.quietPlay &&
3658             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3659             SendToICS(ics_prefix);
3660             SendToICS("set shout 0\n");
3661         }
3662     }
3663     
3664     /* Deal with midgame name changes */
3665     if (!newGame) {
3666         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3667             if (gameInfo.white) free(gameInfo.white);
3668             gameInfo.white = StrSave(white);
3669         }
3670         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3671             if (gameInfo.black) free(gameInfo.black);
3672             gameInfo.black = StrSave(black);
3673         }
3674     }
3675     
3676     /* Throw away game result if anything actually changes in examine mode */
3677     if (gameMode == IcsExamining && !newGame) {
3678         gameInfo.result = GameUnfinished;
3679         if (gameInfo.resultDetails != NULL) {
3680             free(gameInfo.resultDetails);
3681             gameInfo.resultDetails = NULL;
3682         }
3683     }
3684     
3685     /* In pausing && IcsExamining mode, we ignore boards coming
3686        in if they are in a different variation than we are. */
3687     if (pauseExamInvalid) return;
3688     if (pausing && gameMode == IcsExamining) {
3689         if (moveNum <= pauseExamForwardMostMove) {
3690             pauseExamInvalid = TRUE;
3691             forwardMostMove = pauseExamForwardMostMove;
3692             return;
3693         }
3694     }
3695     
3696   if (appData.debugMode) {
3697     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3698   }
3699     /* Parse the board */
3700     for (k = 0; k < ranks; k++) {
3701       for (j = 0; j < files; j++)
3702         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3703       if(gameInfo.holdingsWidth > 1) {
3704            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3705            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3706       }
3707     }
3708     CopyBoard(boards[moveNum], board);
3709     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3710     if (moveNum == 0) {
3711         startedFromSetupPosition =
3712           !CompareBoards(board, initialPosition);
3713         if(startedFromSetupPosition)
3714             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3715     }
3716
3717     /* [HGM] Set castling rights. Take the outermost Rooks,
3718        to make it also work for FRC opening positions. Note that board12
3719        is really defective for later FRC positions, as it has no way to
3720        indicate which Rook can castle if they are on the same side of King.
3721        For the initial position we grant rights to the outermost Rooks,
3722        and remember thos rights, and we then copy them on positions
3723        later in an FRC game. This means WB might not recognize castlings with
3724        Rooks that have moved back to their original position as illegal,
3725        but in ICS mode that is not its job anyway.
3726     */
3727     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3728     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3729
3730         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3731             if(board[0][i] == WhiteRook) j = i;
3732         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3733         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3734             if(board[0][i] == WhiteRook) j = i;
3735         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3736         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3737             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3738         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3739         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3740             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3741         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3742
3743         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3744         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3745             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3746         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3747             if(board[BOARD_HEIGHT-1][k] == bKing)
3748                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3749         if(gameInfo.variant == VariantTwoKings) {
3750             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3751             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3752             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3753         }
3754     } else { int r;
3755         r = boards[moveNum][CASTLING][0] = initialRights[0];
3756         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3757         r = boards[moveNum][CASTLING][1] = initialRights[1];
3758         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3759         r = boards[moveNum][CASTLING][3] = initialRights[3];
3760         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3761         r = boards[moveNum][CASTLING][4] = initialRights[4];
3762         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3763         /* wildcastle kludge: always assume King has rights */
3764         r = boards[moveNum][CASTLING][2] = initialRights[2];
3765         r = boards[moveNum][CASTLING][5] = initialRights[5];
3766     }
3767     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3768     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3769
3770     
3771     if (ics_getting_history == H_GOT_REQ_HEADER ||
3772         ics_getting_history == H_GOT_UNREQ_HEADER) {
3773         /* This was an initial position from a move list, not
3774            the current position */
3775         return;
3776     }
3777     
3778     /* Update currentMove and known move number limits */
3779     newMove = newGame || moveNum > forwardMostMove;
3780
3781     if (newGame) {
3782         forwardMostMove = backwardMostMove = currentMove = moveNum;
3783         if (gameMode == IcsExamining && moveNum == 0) {
3784           /* Workaround for ICS limitation: we are not told the wild
3785              type when starting to examine a game.  But if we ask for
3786              the move list, the move list header will tell us */
3787             ics_getting_history = H_REQUESTED;
3788             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3789             SendToICS(str);
3790         }
3791     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3792                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3793 #if ZIPPY
3794         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3795         /* [HGM] applied this also to an engine that is silently watching        */
3796         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3797             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3798             gameInfo.variant == currentlyInitializedVariant) {
3799           takeback = forwardMostMove - moveNum;
3800           for (i = 0; i < takeback; i++) {
3801             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3802             SendToProgram("undo\n", &first);
3803           }
3804         }
3805 #endif
3806
3807         forwardMostMove = moveNum;
3808         if (!pausing || currentMove > forwardMostMove)
3809           currentMove = forwardMostMove;
3810     } else {
3811         /* New part of history that is not contiguous with old part */ 
3812         if (pausing && gameMode == IcsExamining) {
3813             pauseExamInvalid = TRUE;
3814             forwardMostMove = pauseExamForwardMostMove;
3815             return;
3816         }
3817         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3818 #if ZIPPY
3819             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3820                 // [HGM] when we will receive the move list we now request, it will be
3821                 // fed to the engine from the first move on. So if the engine is not
3822                 // in the initial position now, bring it there.
3823                 InitChessProgram(&first, 0);
3824             }
3825 #endif
3826             ics_getting_history = H_REQUESTED;
3827             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3828             SendToICS(str);
3829         }
3830         forwardMostMove = backwardMostMove = currentMove = moveNum;
3831     }
3832     
3833     /* Update the clocks */
3834     if (strchr(elapsed_time, '.')) {
3835       /* Time is in ms */
3836       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3837       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3838     } else {
3839       /* Time is in seconds */
3840       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3841       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3842     }
3843       
3844
3845 #if ZIPPY
3846     if (appData.zippyPlay && newGame &&
3847         gameMode != IcsObserving && gameMode != IcsIdle &&
3848         gameMode != IcsExamining)
3849       ZippyFirstBoard(moveNum, basetime, increment);
3850 #endif
3851     
3852     /* Put the move on the move list, first converting
3853        to canonical algebraic form. */
3854     if (moveNum > 0) {
3855   if (appData.debugMode) {
3856     if (appData.debugMode) { int f = forwardMostMove;
3857         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3858                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3859                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3860     }
3861     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3862     fprintf(debugFP, "moveNum = %d\n", moveNum);
3863     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3864     setbuf(debugFP, NULL);
3865   }
3866         if (moveNum <= backwardMostMove) {
3867             /* We don't know what the board looked like before
3868                this move.  Punt. */
3869             strcpy(parseList[moveNum - 1], move_str);
3870             strcat(parseList[moveNum - 1], " ");
3871             strcat(parseList[moveNum - 1], elapsed_time);
3872             moveList[moveNum - 1][0] = NULLCHAR;
3873         } else if (strcmp(move_str, "none") == 0) {
3874             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3875             /* Again, we don't know what the board looked like;
3876                this is really the start of the game. */
3877             parseList[moveNum - 1][0] = NULLCHAR;
3878             moveList[moveNum - 1][0] = NULLCHAR;
3879             backwardMostMove = moveNum;
3880             startedFromSetupPosition = TRUE;
3881             fromX = fromY = toX = toY = -1;
3882         } else {
3883           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3884           //                 So we parse the long-algebraic move string in stead of the SAN move
3885           int valid; char buf[MSG_SIZ], *prom;
3886
3887           // str looks something like "Q/a1-a2"; kill the slash
3888           if(str[1] == '/') 
3889                 sprintf(buf, "%c%s", str[0], str+2);
3890           else  strcpy(buf, str); // might be castling
3891           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3892                 strcat(buf, prom); // long move lacks promo specification!
3893           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3894                 if(appData.debugMode) 
3895                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3896                 strcpy(move_str, buf);
3897           }
3898           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3899                                 &fromX, &fromY, &toX, &toY, &promoChar)
3900                || ParseOneMove(buf, moveNum - 1, &moveType,
3901                                 &fromX, &fromY, &toX, &toY, &promoChar);
3902           // end of long SAN patch
3903           if (valid) {
3904             (void) CoordsToAlgebraic(boards[moveNum - 1],
3905                                      PosFlags(moveNum - 1),
3906                                      fromY, fromX, toY, toX, promoChar,
3907                                      parseList[moveNum-1]);
3908             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3909               case MT_NONE:
3910               case MT_STALEMATE:
3911               default:
3912                 break;
3913               case MT_CHECK:
3914                 if(gameInfo.variant != VariantShogi)
3915                     strcat(parseList[moveNum - 1], "+");
3916                 break;
3917               case MT_CHECKMATE:
3918               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3919                 strcat(parseList[moveNum - 1], "#");
3920                 break;
3921             }
3922             strcat(parseList[moveNum - 1], " ");
3923             strcat(parseList[moveNum - 1], elapsed_time);
3924             /* currentMoveString is set as a side-effect of ParseOneMove */
3925             strcpy(moveList[moveNum - 1], currentMoveString);
3926             strcat(moveList[moveNum - 1], "\n");
3927           } else {
3928             /* Move from ICS was illegal!?  Punt. */
3929   if (appData.debugMode) {
3930     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3931     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3932   }
3933             strcpy(parseList[moveNum - 1], move_str);
3934             strcat(parseList[moveNum - 1], " ");
3935             strcat(parseList[moveNum - 1], elapsed_time);
3936             moveList[moveNum - 1][0] = NULLCHAR;
3937             fromX = fromY = toX = toY = -1;
3938           }
3939         }
3940   if (appData.debugMode) {
3941     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3942     setbuf(debugFP, NULL);
3943   }
3944
3945 #if ZIPPY
3946         /* Send move to chess program (BEFORE animating it). */
3947         if (appData.zippyPlay && !newGame && newMove && 
3948            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3949
3950             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3951                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3952                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3953                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3954                             move_str);
3955                     DisplayError(str, 0);
3956                 } else {
3957                     if (first.sendTime) {
3958                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3959                     }
3960                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3961                     if (firstMove && !bookHit) {
3962                         firstMove = FALSE;
3963                         if (first.useColors) {
3964                           SendToProgram(gameMode == IcsPlayingWhite ?
3965                                         "white\ngo\n" :
3966                                         "black\ngo\n", &first);
3967                         } else {
3968                           SendToProgram("go\n", &first);
3969                         }
3970                         first.maybeThinking = TRUE;
3971                     }
3972                 }
3973             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3974               if (moveList[moveNum - 1][0] == NULLCHAR) {
3975                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3976                 DisplayError(str, 0);
3977               } else {
3978                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3979                 SendMoveToProgram(moveNum - 1, &first);
3980               }
3981             }
3982         }
3983 #endif
3984     }
3985
3986     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3987         /* If move comes from a remote source, animate it.  If it
3988            isn't remote, it will have already been animated. */
3989         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3990             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3991         }
3992         if (!pausing && appData.highlightLastMove) {
3993             SetHighlights(fromX, fromY, toX, toY);
3994         }
3995     }
3996     
3997     /* Start the clocks */
3998     whiteFlag = blackFlag = FALSE;
3999     appData.clockMode = !(basetime == 0 && increment == 0);
4000     if (ticking == 0) {
4001       ics_clock_paused = TRUE;
4002       StopClocks();
4003     } else if (ticking == 1) {
4004       ics_clock_paused = FALSE;
4005     }
4006     if (gameMode == IcsIdle ||
4007         relation == RELATION_OBSERVING_STATIC ||
4008         relation == RELATION_EXAMINING ||
4009         ics_clock_paused)
4010       DisplayBothClocks();
4011     else
4012       StartClocks();
4013     
4014     /* Display opponents and material strengths */
4015     if (gameInfo.variant != VariantBughouse &&
4016         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4017         if (tinyLayout || smallLayout) {
4018             if(gameInfo.variant == VariantNormal)
4019                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4020                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4021                     basetime, increment);
4022             else
4023                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4024                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4025                     basetime, increment, (int) gameInfo.variant);
4026         } else {
4027             if(gameInfo.variant == VariantNormal)
4028                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4029                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4030                     basetime, increment);
4031             else
4032                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4033                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4034                     basetime, increment, VariantName(gameInfo.variant));
4035         }
4036         DisplayTitle(str);
4037   if (appData.debugMode) {
4038     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4039   }
4040     }
4041
4042    
4043     /* Display the board */
4044     if (!pausing && !appData.noGUI) {
4045       
4046       if (appData.premove)
4047           if (!gotPremove || 
4048              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4049              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4050               ClearPremoveHighlights();
4051
4052       DrawPosition(FALSE, boards[currentMove]);
4053       DisplayMove(moveNum - 1);
4054       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4055             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4056               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4057         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4058       }
4059     }
4060
4061     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4062 #if ZIPPY
4063     if(bookHit) { // [HGM] book: simulate book reply
4064         static char bookMove[MSG_SIZ]; // a bit generous?
4065
4066         programStats.nodes = programStats.depth = programStats.time = 
4067         programStats.score = programStats.got_only_move = 0;
4068         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4069
4070         strcpy(bookMove, "move ");
4071         strcat(bookMove, bookHit);
4072         HandleMachineMove(bookMove, &first);
4073     }
4074 #endif
4075 }
4076
4077 void
4078 GetMoveListEvent()
4079 {
4080     char buf[MSG_SIZ];
4081     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4082         ics_getting_history = H_REQUESTED;
4083         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4084         SendToICS(buf);
4085     }
4086 }
4087
4088 void
4089 AnalysisPeriodicEvent(force)
4090      int force;
4091 {
4092     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4093          && !force) || !appData.periodicUpdates)
4094       return;
4095
4096     /* Send . command to Crafty to collect stats */
4097     SendToProgram(".\n", &first);
4098
4099     /* Don't send another until we get a response (this makes
4100        us stop sending to old Crafty's which don't understand
4101        the "." command (sending illegal cmds resets node count & time,
4102        which looks bad)) */
4103     programStats.ok_to_send = 0;
4104 }
4105
4106 void ics_update_width(new_width)
4107         int new_width;
4108 {
4109         ics_printf("set width %d\n", new_width);
4110 }
4111
4112 void
4113 SendMoveToProgram(moveNum, cps)
4114      int moveNum;
4115      ChessProgramState *cps;
4116 {
4117     char buf[MSG_SIZ];
4118
4119     if (cps->useUsermove) {
4120       SendToProgram("usermove ", cps);
4121     }
4122     if (cps->useSAN) {
4123       char *space;
4124       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4125         int len = space - parseList[moveNum];
4126         memcpy(buf, parseList[moveNum], len);
4127         buf[len++] = '\n';
4128         buf[len] = NULLCHAR;
4129       } else {
4130         sprintf(buf, "%s\n", parseList[moveNum]);
4131       }
4132       SendToProgram(buf, cps);
4133     } else {
4134       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4135         AlphaRank(moveList[moveNum], 4);
4136         SendToProgram(moveList[moveNum], cps);
4137         AlphaRank(moveList[moveNum], 4); // and back
4138       } else
4139       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4140        * the engine. It would be nice to have a better way to identify castle 
4141        * moves here. */
4142       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4143                                                                          && cps->useOOCastle) {
4144         int fromX = moveList[moveNum][0] - AAA; 
4145         int fromY = moveList[moveNum][1] - ONE;
4146         int toX = moveList[moveNum][2] - AAA; 
4147         int toY = moveList[moveNum][3] - ONE;
4148         if((boards[moveNum][fromY][fromX] == WhiteKing 
4149             && boards[moveNum][toY][toX] == WhiteRook)
4150            || (boards[moveNum][fromY][fromX] == BlackKing 
4151                && boards[moveNum][toY][toX] == BlackRook)) {
4152           if(toX > fromX) SendToProgram("O-O\n", cps);
4153           else SendToProgram("O-O-O\n", cps);
4154         }
4155         else SendToProgram(moveList[moveNum], cps);
4156       }
4157       else SendToProgram(moveList[moveNum], cps);
4158       /* End of additions by Tord */
4159     }
4160
4161     /* [HGM] setting up the opening has brought engine in force mode! */
4162     /*       Send 'go' if we are in a mode where machine should play. */
4163     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4164         (gameMode == TwoMachinesPlay   ||
4165 #ifdef ZIPPY
4166          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4167 #endif
4168          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4169         SendToProgram("go\n", cps);
4170   if (appData.debugMode) {
4171     fprintf(debugFP, "(extra)\n");
4172   }
4173     }
4174     setboardSpoiledMachineBlack = 0;
4175 }
4176
4177 void
4178 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4179      ChessMove moveType;
4180      int fromX, fromY, toX, toY;
4181 {
4182     char user_move[MSG_SIZ];
4183
4184     switch (moveType) {
4185       default:
4186         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4187                 (int)moveType, fromX, fromY, toX, toY);
4188         DisplayError(user_move + strlen("say "), 0);
4189         break;
4190       case WhiteKingSideCastle:
4191       case BlackKingSideCastle:
4192       case WhiteQueenSideCastleWild:
4193       case BlackQueenSideCastleWild:
4194       /* PUSH Fabien */
4195       case WhiteHSideCastleFR:
4196       case BlackHSideCastleFR:
4197       /* POP Fabien */
4198         sprintf(user_move, "o-o\n");
4199         break;
4200       case WhiteQueenSideCastle:
4201       case BlackQueenSideCastle:
4202       case WhiteKingSideCastleWild:
4203       case BlackKingSideCastleWild:
4204       /* PUSH Fabien */
4205       case WhiteASideCastleFR:
4206       case BlackASideCastleFR:
4207       /* POP Fabien */
4208         sprintf(user_move, "o-o-o\n");
4209         break;
4210       case WhitePromotionQueen:
4211       case BlackPromotionQueen:
4212       case WhitePromotionRook:
4213       case BlackPromotionRook:
4214       case WhitePromotionBishop:
4215       case BlackPromotionBishop:
4216       case WhitePromotionKnight:
4217       case BlackPromotionKnight:
4218       case WhitePromotionKing:
4219       case BlackPromotionKing:
4220       case WhitePromotionChancellor:
4221       case BlackPromotionChancellor:
4222       case WhitePromotionArchbishop:
4223       case BlackPromotionArchbishop:
4224         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4225             sprintf(user_move, "%c%c%c%c=%c\n",
4226                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4227                 PieceToChar(WhiteFerz));
4228         else if(gameInfo.variant == VariantGreat)
4229             sprintf(user_move, "%c%c%c%c=%c\n",
4230                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4231                 PieceToChar(WhiteMan));
4232         else
4233             sprintf(user_move, "%c%c%c%c=%c\n",
4234                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4235                 PieceToChar(PromoPiece(moveType)));
4236         break;
4237       case WhiteDrop:
4238       case BlackDrop:
4239         sprintf(user_move, "%c@%c%c\n",
4240                 ToUpper(PieceToChar((ChessSquare) fromX)),
4241                 AAA + toX, ONE + toY);
4242         break;
4243       case NormalMove:
4244       case WhiteCapturesEnPassant:
4245       case BlackCapturesEnPassant:
4246       case IllegalMove:  /* could be a variant we don't quite understand */
4247         sprintf(user_move, "%c%c%c%c\n",
4248                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4249         break;
4250     }
4251     SendToICS(user_move);
4252     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4253         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4254 }
4255
4256 void
4257 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4258      int rf, ff, rt, ft;
4259      char promoChar;
4260      char move[7];
4261 {
4262     if (rf == DROP_RANK) {
4263         sprintf(move, "%c@%c%c\n",
4264                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4265     } else {
4266         if (promoChar == 'x' || promoChar == NULLCHAR) {
4267             sprintf(move, "%c%c%c%c\n",
4268                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4269         } else {
4270             sprintf(move, "%c%c%c%c%c\n",
4271                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4272         }
4273     }
4274 }
4275
4276 void
4277 ProcessICSInitScript(f)
4278      FILE *f;
4279 {
4280     char buf[MSG_SIZ];
4281
4282     while (fgets(buf, MSG_SIZ, f)) {
4283         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4284     }
4285
4286     fclose(f);
4287 }
4288
4289
4290 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4291 void
4292 AlphaRank(char *move, int n)
4293 {
4294 //    char *p = move, c; int x, y;
4295
4296     if (appData.debugMode) {
4297         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4298     }
4299
4300     if(move[1]=='*' && 
4301        move[2]>='0' && move[2]<='9' &&
4302        move[3]>='a' && move[3]<='x'    ) {
4303         move[1] = '@';
4304         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4305         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4306     } else
4307     if(move[0]>='0' && move[0]<='9' &&
4308        move[1]>='a' && move[1]<='x' &&
4309        move[2]>='0' && move[2]<='9' &&
4310        move[3]>='a' && move[3]<='x'    ) {
4311         /* input move, Shogi -> normal */
4312         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4313         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4314         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4315         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4316     } else
4317     if(move[1]=='@' &&
4318        move[3]>='0' && move[3]<='9' &&
4319        move[2]>='a' && move[2]<='x'    ) {
4320         move[1] = '*';
4321         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4322         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4323     } else
4324     if(
4325        move[0]>='a' && move[0]<='x' &&
4326        move[3]>='0' && move[3]<='9' &&
4327        move[2]>='a' && move[2]<='x'    ) {
4328          /* output move, normal -> Shogi */
4329         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4330         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4331         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4332         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4333         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4334     }
4335     if (appData.debugMode) {
4336         fprintf(debugFP, "   out = '%s'\n", move);
4337     }
4338 }
4339
4340 /* Parser for moves from gnuchess, ICS, or user typein box */
4341 Boolean
4342 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4343      char *move;
4344      int moveNum;
4345      ChessMove *moveType;
4346      int *fromX, *fromY, *toX, *toY;
4347      char *promoChar;
4348 {       
4349     if (appData.debugMode) {
4350         fprintf(debugFP, "move to parse: %s\n", move);
4351     }
4352     *moveType = yylexstr(moveNum, move);
4353
4354     switch (*moveType) {
4355       case WhitePromotionChancellor:
4356       case BlackPromotionChancellor:
4357       case WhitePromotionArchbishop:
4358       case BlackPromotionArchbishop:
4359       case WhitePromotionQueen:
4360       case BlackPromotionQueen:
4361       case WhitePromotionRook:
4362       case BlackPromotionRook:
4363       case WhitePromotionBishop:
4364       case BlackPromotionBishop:
4365       case WhitePromotionKnight:
4366       case BlackPromotionKnight:
4367       case WhitePromotionKing:
4368       case BlackPromotionKing:
4369       case NormalMove:
4370       case WhiteCapturesEnPassant:
4371       case BlackCapturesEnPassant:
4372       case WhiteKingSideCastle:
4373       case WhiteQueenSideCastle:
4374       case BlackKingSideCastle:
4375       case BlackQueenSideCastle:
4376       case WhiteKingSideCastleWild:
4377       case WhiteQueenSideCastleWild:
4378       case BlackKingSideCastleWild:
4379       case BlackQueenSideCastleWild:
4380       /* Code added by Tord: */
4381       case WhiteHSideCastleFR:
4382       case WhiteASideCastleFR:
4383       case BlackHSideCastleFR:
4384       case BlackASideCastleFR:
4385       /* End of code added by Tord */
4386       case IllegalMove:         /* bug or odd chess variant */
4387         *fromX = currentMoveString[0] - AAA;
4388         *fromY = currentMoveString[1] - ONE;
4389         *toX = currentMoveString[2] - AAA;
4390         *toY = currentMoveString[3] - ONE;
4391         *promoChar = currentMoveString[4];
4392         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4393             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4394     if (appData.debugMode) {
4395         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4396     }
4397             *fromX = *fromY = *toX = *toY = 0;
4398             return FALSE;
4399         }
4400         if (appData.testLegality) {
4401           return (*moveType != IllegalMove);
4402         } else {
4403           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4404                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4405         }
4406
4407       case WhiteDrop:
4408       case BlackDrop:
4409         *fromX = *moveType == WhiteDrop ?
4410           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4411           (int) CharToPiece(ToLower(currentMoveString[0]));
4412         *fromY = DROP_RANK;
4413         *toX = currentMoveString[2] - AAA;
4414         *toY = currentMoveString[3] - ONE;
4415         *promoChar = NULLCHAR;
4416         return TRUE;
4417
4418       case AmbiguousMove:
4419       case ImpossibleMove:
4420       case (ChessMove) 0:       /* end of file */
4421       case ElapsedTime:
4422       case Comment:
4423       case PGNTag:
4424       case NAG:
4425       case WhiteWins:
4426       case BlackWins:
4427       case GameIsDrawn:
4428       default:
4429     if (appData.debugMode) {
4430         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4431     }
4432         /* bug? */
4433         *fromX = *fromY = *toX = *toY = 0;
4434         *promoChar = NULLCHAR;
4435         return FALSE;
4436     }
4437 }
4438
4439
4440 void
4441 ParsePV(char *pv)
4442 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4443   int fromX, fromY, toX, toY; char promoChar;
4444   ChessMove moveType;
4445   Boolean valid;
4446   int nr = 0;
4447
4448   endPV = forwardMostMove;
4449   do {
4450     while(*pv == ' ') pv++;
4451     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4452     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4453 if(appData.debugMode){
4454 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4455 }
4456     if(!valid && nr == 0 &&
4457        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4458         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4459     }
4460     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4461     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4462     nr++;
4463     if(endPV+1 > framePtr) break; // no space, truncate
4464     if(!valid) break;
4465     endPV++;
4466     CopyBoard(boards[endPV], boards[endPV-1]);
4467     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4468     moveList[endPV-1][0] = fromX + AAA;
4469     moveList[endPV-1][1] = fromY + ONE;
4470     moveList[endPV-1][2] = toX + AAA;
4471     moveList[endPV-1][3] = toY + ONE;
4472     parseList[endPV-1][0] = NULLCHAR;
4473   } while(valid);
4474   currentMove = endPV;
4475   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4476   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4477                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4478   DrawPosition(TRUE, boards[currentMove]);
4479 }
4480
4481 static int lastX, lastY;
4482
4483 Boolean
4484 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4485 {
4486         int startPV;
4487
4488         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4489         lastX = x; lastY = y;
4490         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4491         startPV = index;
4492       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4493       index = startPV;
4494         while(buf[index] && buf[index] != '\n') index++;
4495         buf[index] = 0;
4496         ParsePV(buf+startPV);
4497         *start = startPV; *end = index-1;
4498         return TRUE;
4499 }
4500
4501 Boolean
4502 LoadPV(int x, int y)
4503 { // called on right mouse click to load PV
4504   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4505   lastX = x; lastY = y;
4506   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4507   return TRUE;
4508 }
4509
4510 void
4511 UnLoadPV()
4512 {
4513   if(endPV < 0) return;
4514   endPV = -1;
4515   currentMove = forwardMostMove;
4516   ClearPremoveHighlights();
4517   DrawPosition(TRUE, boards[currentMove]);
4518 }
4519
4520 void
4521 MovePV(int x, int y, int h)
4522 { // step through PV based on mouse coordinates (called on mouse move)
4523   int margin = h>>3, step = 0;
4524
4525   if(endPV < 0) return;
4526   // we must somehow check if right button is still down (might be released off board!)
4527   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4528   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4529   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4530   if(!step) return;
4531   lastX = x; lastY = y;
4532   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4533   currentMove += step;
4534   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4535   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4536                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4537   DrawPosition(FALSE, boards[currentMove]);
4538 }
4539
4540
4541 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4542 // All positions will have equal probability, but the current method will not provide a unique
4543 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4544 #define DARK 1
4545 #define LITE 2
4546 #define ANY 3
4547
4548 int squaresLeft[4];
4549 int piecesLeft[(int)BlackPawn];
4550 int seed, nrOfShuffles;
4551
4552 void GetPositionNumber()
4553 {       // sets global variable seed
4554         int i;
4555
4556         seed = appData.defaultFrcPosition;
4557         if(seed < 0) { // randomize based on time for negative FRC position numbers
4558                 for(i=0; i<50; i++) seed += random();
4559                 seed = random() ^ random() >> 8 ^ random() << 8;
4560                 if(seed<0) seed = -seed;
4561         }
4562 }
4563
4564 int put(Board board, int pieceType, int rank, int n, int shade)
4565 // put the piece on the (n-1)-th empty squares of the given shade
4566 {
4567         int i;
4568
4569         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4570                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4571                         board[rank][i] = (ChessSquare) pieceType;
4572                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4573                         squaresLeft[ANY]--;
4574                         piecesLeft[pieceType]--; 
4575                         return i;
4576                 }
4577         }
4578         return -1;
4579 }
4580
4581
4582 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4583 // calculate where the next piece goes, (any empty square), and put it there
4584 {
4585         int i;
4586
4587         i = seed % squaresLeft[shade];
4588         nrOfShuffles *= squaresLeft[shade];
4589         seed /= squaresLeft[shade];
4590         put(board, pieceType, rank, i, shade);
4591 }
4592
4593 void AddTwoPieces(Board board, int pieceType, int rank)
4594 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4595 {
4596         int i, n=squaresLeft[ANY], j=n-1, k;
4597
4598         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4599         i = seed % k;  // pick one
4600         nrOfShuffles *= k;
4601         seed /= k;
4602         while(i >= j) i -= j--;
4603         j = n - 1 - j; i += j;
4604         put(board, pieceType, rank, j, ANY);
4605         put(board, pieceType, rank, i, ANY);
4606 }
4607
4608 void SetUpShuffle(Board board, int number)
4609 {
4610         int i, p, first=1;
4611
4612         GetPositionNumber(); nrOfShuffles = 1;
4613
4614         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4615         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4616         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4617
4618         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4619
4620         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4621             p = (int) board[0][i];
4622             if(p < (int) BlackPawn) piecesLeft[p] ++;
4623             board[0][i] = EmptySquare;
4624         }
4625
4626         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4627             // shuffles restricted to allow normal castling put KRR first
4628             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4629                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4630             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4631                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4632             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4633                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4634             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4635                 put(board, WhiteRook, 0, 0, ANY);
4636             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4637         }
4638
4639         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4640             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4641             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4642                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4643                 while(piecesLeft[p] >= 2) {
4644                     AddOnePiece(board, p, 0, LITE);
4645                     AddOnePiece(board, p, 0, DARK);
4646                 }
4647                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4648             }
4649
4650         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4651             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4652             // but we leave King and Rooks for last, to possibly obey FRC restriction
4653             if(p == (int)WhiteRook) continue;
4654             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4655             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4656         }
4657
4658         // now everything is placed, except perhaps King (Unicorn) and Rooks
4659
4660         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4661             // Last King gets castling rights
4662             while(piecesLeft[(int)WhiteUnicorn]) {
4663                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4664                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4665             }
4666
4667             while(piecesLeft[(int)WhiteKing]) {
4668                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4669                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4670             }
4671
4672
4673         } else {
4674             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4675             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4676         }
4677
4678         // Only Rooks can be left; simply place them all
4679         while(piecesLeft[(int)WhiteRook]) {
4680                 i = put(board, WhiteRook, 0, 0, ANY);
4681                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4682                         if(first) {
4683                                 first=0;
4684                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4685                         }
4686                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4687                 }
4688         }
4689         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4690             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4691         }
4692
4693         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4694 }
4695
4696 int SetCharTable( char *table, const char * map )
4697 /* [HGM] moved here from winboard.c because of its general usefulness */
4698 /*       Basically a safe strcpy that uses the last character as King */
4699 {
4700     int result = FALSE; int NrPieces;
4701
4702     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4703                     && NrPieces >= 12 && !(NrPieces&1)) {
4704         int i; /* [HGM] Accept even length from 12 to 34 */
4705
4706         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4707         for( i=0; i<NrPieces/2-1; i++ ) {
4708             table[i] = map[i];
4709             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4710         }
4711         table[(int) WhiteKing]  = map[NrPieces/2-1];
4712         table[(int) BlackKing]  = map[NrPieces-1];
4713
4714         result = TRUE;
4715     }
4716
4717     return result;
4718 }
4719
4720 void Prelude(Board board)
4721 {       // [HGM] superchess: random selection of exo-pieces
4722         int i, j, k; ChessSquare p; 
4723         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4724
4725         GetPositionNumber(); // use FRC position number
4726
4727         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4728             SetCharTable(pieceToChar, appData.pieceToCharTable);
4729             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4730                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4731         }
4732
4733         j = seed%4;                 seed /= 4; 
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 >= j); seed /= 3; 
4738         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3;                 seed /= 3; 
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%2 + (seed%2 >= j); seed /= 2; 
4746         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4747         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4748         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4749         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4750         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4751         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4752         put(board, exoPieces[0],    0, 0, ANY);
4753         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4754 }
4755
4756 void
4757 InitPosition(redraw)
4758      int redraw;
4759 {
4760     ChessSquare (* pieces)[BOARD_FILES];
4761     int i, j, pawnRow, overrule,
4762     oldx = gameInfo.boardWidth,
4763     oldy = gameInfo.boardHeight,
4764     oldh = gameInfo.holdingsWidth,
4765     oldv = gameInfo.variant;
4766
4767     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4768
4769     /* [AS] Initialize pv info list [HGM] and game status */
4770     {
4771         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4772             pvInfoList[i].depth = 0;
4773             boards[i][EP_STATUS] = EP_NONE;
4774             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4775         }
4776
4777         initialRulePlies = 0; /* 50-move counter start */
4778
4779         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4780         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4781     }
4782
4783     
4784     /* [HGM] logic here is completely changed. In stead of full positions */
4785     /* the initialized data only consist of the two backranks. The switch */
4786     /* selects which one we will use, which is than copied to the Board   */
4787     /* initialPosition, which for the rest is initialized by Pawns and    */
4788     /* empty squares. This initial position is then copied to boards[0],  */
4789     /* possibly after shuffling, so that it remains available.            */
4790
4791     gameInfo.holdingsWidth = 0; /* default board sizes */
4792     gameInfo.boardWidth    = 8;
4793     gameInfo.boardHeight   = 8;
4794     gameInfo.holdingsSize  = 0;
4795     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4796     for(i=0; i<BOARD_FILES-2; i++)
4797       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4798     initialPosition[EP_STATUS] = EP_NONE;
4799     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4800
4801     switch (gameInfo.variant) {
4802     case VariantFischeRandom:
4803       shuffleOpenings = TRUE;
4804     default:
4805       pieces = FIDEArray;
4806       break;
4807     case VariantShatranj:
4808       pieces = ShatranjArray;
4809       nrCastlingRights = 0;
4810       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4811       break;
4812     case VariantMakruk:
4813       pieces = makrukArray;
4814       nrCastlingRights = 0;
4815       startedFromSetupPosition = TRUE;
4816       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4817       break;
4818     case VariantTwoKings:
4819       pieces = twoKingsArray;
4820       break;
4821     case VariantCapaRandom:
4822       shuffleOpenings = TRUE;
4823     case VariantCapablanca:
4824       pieces = CapablancaArray;
4825       gameInfo.boardWidth = 10;
4826       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4827       break;
4828     case VariantGothic:
4829       pieces = GothicArray;
4830       gameInfo.boardWidth = 10;
4831       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4832       break;
4833     case VariantJanus:
4834       pieces = JanusArray;
4835       gameInfo.boardWidth = 10;
4836       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4837       nrCastlingRights = 6;
4838         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4839         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4840         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4841         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4842         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4843         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4844       break;
4845     case VariantFalcon:
4846       pieces = FalconArray;
4847       gameInfo.boardWidth = 10;
4848       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4849       break;
4850     case VariantXiangqi:
4851       pieces = XiangqiArray;
4852       gameInfo.boardWidth  = 9;
4853       gameInfo.boardHeight = 10;
4854       nrCastlingRights = 0;
4855       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4856       break;
4857     case VariantShogi:
4858       pieces = ShogiArray;
4859       gameInfo.boardWidth  = 9;
4860       gameInfo.boardHeight = 9;
4861       gameInfo.holdingsSize = 7;
4862       nrCastlingRights = 0;
4863       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4864       break;
4865     case VariantCourier:
4866       pieces = CourierArray;
4867       gameInfo.boardWidth  = 12;
4868       nrCastlingRights = 0;
4869       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4870       break;
4871     case VariantKnightmate:
4872       pieces = KnightmateArray;
4873       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4874       break;
4875     case VariantFairy:
4876       pieces = fairyArray;
4877       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4878       break;
4879     case VariantGreat:
4880       pieces = GreatArray;
4881       gameInfo.boardWidth = 10;
4882       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4883       gameInfo.holdingsSize = 8;
4884       break;
4885     case VariantSuper:
4886       pieces = FIDEArray;
4887       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4888       gameInfo.holdingsSize = 8;
4889       startedFromSetupPosition = TRUE;
4890       break;
4891     case VariantCrazyhouse:
4892     case VariantBughouse:
4893       pieces = FIDEArray;
4894       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4895       gameInfo.holdingsSize = 5;
4896       break;
4897     case VariantWildCastle:
4898       pieces = FIDEArray;
4899       /* !!?shuffle with kings guaranteed to be on d or e file */
4900       shuffleOpenings = 1;
4901       break;
4902     case VariantNoCastle:
4903       pieces = FIDEArray;
4904       nrCastlingRights = 0;
4905       /* !!?unconstrained back-rank shuffle */
4906       shuffleOpenings = 1;
4907       break;
4908     }
4909
4910     overrule = 0;
4911     if(appData.NrFiles >= 0) {
4912         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4913         gameInfo.boardWidth = appData.NrFiles;
4914     }
4915     if(appData.NrRanks >= 0) {
4916         gameInfo.boardHeight = appData.NrRanks;
4917     }
4918     if(appData.holdingsSize >= 0) {
4919         i = appData.holdingsSize;
4920         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4921         gameInfo.holdingsSize = i;
4922     }
4923     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4924     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4925         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4926
4927     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4928     if(pawnRow < 1) pawnRow = 1;
4929     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4930
4931     /* User pieceToChar list overrules defaults */
4932     if(appData.pieceToCharTable != NULL)
4933         SetCharTable(pieceToChar, appData.pieceToCharTable);
4934
4935     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4936
4937         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4938             s = (ChessSquare) 0; /* account holding counts in guard band */
4939         for( i=0; i<BOARD_HEIGHT; i++ )
4940             initialPosition[i][j] = s;
4941
4942         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4943         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4944         initialPosition[pawnRow][j] = WhitePawn;
4945         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4946         if(gameInfo.variant == VariantXiangqi) {
4947             if(j&1) {
4948                 initialPosition[pawnRow][j] = 
4949                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4950                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4951                    initialPosition[2][j] = WhiteCannon;
4952                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4953                 }
4954             }
4955         }
4956         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4957     }
4958     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4959
4960             j=BOARD_LEFT+1;
4961             initialPosition[1][j] = WhiteBishop;
4962             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4963             j=BOARD_RGHT-2;
4964             initialPosition[1][j] = WhiteRook;
4965             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4966     }
4967
4968     if( nrCastlingRights == -1) {
4969         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4970         /*       This sets default castling rights from none to normal corners   */
4971         /* Variants with other castling rights must set them themselves above    */
4972         nrCastlingRights = 6;
4973        
4974         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4975         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4976         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4977         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4978         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4979         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4980      }
4981
4982      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4983      if(gameInfo.variant == VariantGreat) { // promotion commoners
4984         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4985         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4986         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4987         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4988      }
4989   if (appData.debugMode) {
4990     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4991   }
4992     if(shuffleOpenings) {
4993         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4994         startedFromSetupPosition = TRUE;
4995     }
4996     if(startedFromPositionFile) {
4997       /* [HGM] loadPos: use PositionFile for every new game */
4998       CopyBoard(initialPosition, filePosition);
4999       for(i=0; i<nrCastlingRights; i++)
5000           initialRights[i] = filePosition[CASTLING][i];
5001       startedFromSetupPosition = TRUE;
5002     }
5003
5004     CopyBoard(boards[0], initialPosition);
5005
5006     if(oldx != gameInfo.boardWidth ||
5007        oldy != gameInfo.boardHeight ||
5008        oldh != gameInfo.holdingsWidth
5009 #ifdef GOTHIC
5010        || oldv == VariantGothic ||        // For licensing popups
5011        gameInfo.variant == VariantGothic
5012 #endif
5013 #ifdef FALCON
5014        || oldv == VariantFalcon ||
5015        gameInfo.variant == VariantFalcon
5016 #endif
5017                                          )
5018             InitDrawingSizes(-2 ,0);
5019
5020     if (redraw)
5021       DrawPosition(TRUE, boards[currentMove]);
5022 }
5023
5024 void
5025 SendBoard(cps, moveNum)
5026      ChessProgramState *cps;
5027      int moveNum;
5028 {
5029     char message[MSG_SIZ];
5030     
5031     if (cps->useSetboard) {
5032       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5033       sprintf(message, "setboard %s\n", fen);
5034       SendToProgram(message, cps);
5035       free(fen);
5036
5037     } else {
5038       ChessSquare *bp;
5039       int i, j;
5040       /* Kludge to set black to move, avoiding the troublesome and now
5041        * deprecated "black" command.
5042        */
5043       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5044
5045       SendToProgram("edit\n", cps);
5046       SendToProgram("#\n", cps);
5047       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5048         bp = &boards[moveNum][i][BOARD_LEFT];
5049         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5050           if ((int) *bp < (int) BlackPawn) {
5051             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5052                     AAA + j, ONE + i);
5053             if(message[0] == '+' || message[0] == '~') {
5054                 sprintf(message, "%c%c%c+\n",
5055                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5056                         AAA + j, ONE + i);
5057             }
5058             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5059                 message[1] = BOARD_RGHT   - 1 - j + '1';
5060                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5061             }
5062             SendToProgram(message, cps);
5063           }
5064         }
5065       }
5066     
5067       SendToProgram("c\n", cps);
5068       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5069         bp = &boards[moveNum][i][BOARD_LEFT];
5070         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5071           if (((int) *bp != (int) EmptySquare)
5072               && ((int) *bp >= (int) BlackPawn)) {
5073             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5074                     AAA + j, ONE + i);
5075             if(message[0] == '+' || message[0] == '~') {
5076                 sprintf(message, "%c%c%c+\n",
5077                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5078                         AAA + j, ONE + i);
5079             }
5080             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5081                 message[1] = BOARD_RGHT   - 1 - j + '1';
5082                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5083             }
5084             SendToProgram(message, cps);
5085           }
5086         }
5087       }
5088     
5089       SendToProgram(".\n", cps);
5090     }
5091     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5092 }
5093
5094 int
5095 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5096 {
5097     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5098     /* [HGM] add Shogi promotions */
5099     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5100     ChessSquare piece;
5101     ChessMove moveType;
5102     Boolean premove;
5103
5104     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5105     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5106
5107     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5108       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5109         return FALSE;
5110
5111     piece = boards[currentMove][fromY][fromX];
5112     if(gameInfo.variant == VariantShogi) {
5113         promotionZoneSize = 3;
5114         highestPromotingPiece = (int)WhiteFerz;
5115     } else if(gameInfo.variant == VariantMakruk) {
5116         promotionZoneSize = 3;
5117     }
5118
5119     // next weed out all moves that do not touch the promotion zone at all
5120     if((int)piece >= BlackPawn) {
5121         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5122              return FALSE;
5123         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5124     } else {
5125         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5126            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5127     }
5128
5129     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5130
5131     // weed out mandatory Shogi promotions
5132     if(gameInfo.variant == VariantShogi) {
5133         if(piece >= BlackPawn) {
5134             if(toY == 0 && piece == BlackPawn ||
5135                toY == 0 && piece == BlackQueen ||
5136                toY <= 1 && piece == BlackKnight) {
5137                 *promoChoice = '+';
5138                 return FALSE;
5139             }
5140         } else {
5141             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5142                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5143                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5144                 *promoChoice = '+';
5145                 return FALSE;
5146             }
5147         }
5148     }
5149
5150     // weed out obviously illegal Pawn moves
5151     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5152         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5153         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5154         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5155         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5156         // note we are not allowed to test for valid (non-)capture, due to premove
5157     }
5158
5159     // we either have a choice what to promote to, or (in Shogi) whether to promote
5160     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5161         *promoChoice = PieceToChar(BlackFerz);  // no choice
5162         return FALSE;
5163     }
5164     if(appData.alwaysPromoteToQueen) { // predetermined
5165         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5166              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5167         else *promoChoice = PieceToChar(BlackQueen);
5168         return FALSE;
5169     }
5170
5171     // suppress promotion popup on illegal moves that are not premoves
5172     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5173               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5174     if(appData.testLegality && !premove) {
5175         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5176                         fromY, fromX, toY, toX, NULLCHAR);
5177         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5178            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5179             return FALSE;
5180     }
5181
5182     return TRUE;
5183 }
5184
5185 int
5186 InPalace(row, column)
5187      int row, column;
5188 {   /* [HGM] for Xiangqi */
5189     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5190          column < (BOARD_WIDTH + 4)/2 &&
5191          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5192     return FALSE;
5193 }
5194
5195 int
5196 PieceForSquare (x, y)
5197      int x;
5198      int y;
5199 {
5200   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5201      return -1;
5202   else
5203      return boards[currentMove][y][x];
5204 }
5205
5206 int
5207 OKToStartUserMove(x, y)
5208      int x, y;
5209 {
5210     ChessSquare from_piece;
5211     int white_piece;
5212
5213     if (matchMode) return FALSE;
5214     if (gameMode == EditPosition) return TRUE;
5215
5216     if (x >= 0 && y >= 0)
5217       from_piece = boards[currentMove][y][x];
5218     else
5219       from_piece = EmptySquare;
5220
5221     if (from_piece == EmptySquare) return FALSE;
5222
5223     white_piece = (int)from_piece >= (int)WhitePawn &&
5224       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5225
5226     switch (gameMode) {
5227       case PlayFromGameFile:
5228       case AnalyzeFile:
5229       case TwoMachinesPlay:
5230       case EndOfGame:
5231         return FALSE;
5232
5233       case IcsObserving:
5234       case IcsIdle:
5235         return FALSE;
5236
5237       case MachinePlaysWhite:
5238       case IcsPlayingBlack:
5239         if (appData.zippyPlay) return FALSE;
5240         if (white_piece) {
5241             DisplayMoveError(_("You are playing Black"));
5242             return FALSE;
5243         }
5244         break;
5245
5246       case MachinePlaysBlack:
5247       case IcsPlayingWhite:
5248         if (appData.zippyPlay) return FALSE;
5249         if (!white_piece) {
5250             DisplayMoveError(_("You are playing White"));
5251             return FALSE;
5252         }
5253         break;
5254
5255       case EditGame:
5256         if (!white_piece && WhiteOnMove(currentMove)) {
5257             DisplayMoveError(_("It is White's turn"));
5258             return FALSE;
5259         }           
5260         if (white_piece && !WhiteOnMove(currentMove)) {
5261             DisplayMoveError(_("It is Black's turn"));
5262             return FALSE;
5263         }           
5264         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5265             /* Editing correspondence game history */
5266             /* Could disallow this or prompt for confirmation */
5267             cmailOldMove = -1;
5268         }
5269         break;
5270
5271       case BeginningOfGame:
5272         if (appData.icsActive) return FALSE;
5273         if (!appData.noChessProgram) {
5274             if (!white_piece) {
5275                 DisplayMoveError(_("You are playing White"));
5276                 return FALSE;
5277             }
5278         }
5279         break;
5280         
5281       case Training:
5282         if (!white_piece && WhiteOnMove(currentMove)) {
5283             DisplayMoveError(_("It is White's turn"));
5284             return FALSE;
5285         }           
5286         if (white_piece && !WhiteOnMove(currentMove)) {
5287             DisplayMoveError(_("It is Black's turn"));
5288             return FALSE;
5289         }           
5290         break;
5291
5292       default:
5293       case IcsExamining:
5294         break;
5295     }
5296     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5297         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5298         && gameMode != AnalyzeFile && gameMode != Training) {
5299         DisplayMoveError(_("Displayed position is not current"));
5300         return FALSE;
5301     }
5302     return TRUE;
5303 }
5304
5305 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5306 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5307 int lastLoadGameUseList = FALSE;
5308 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5309 ChessMove lastLoadGameStart = (ChessMove) 0;
5310
5311 ChessMove
5312 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5313      int fromX, fromY, toX, toY;
5314      int promoChar;
5315      Boolean captureOwn;
5316 {
5317     ChessMove moveType;
5318     ChessSquare pdown, pup;
5319
5320     /* Check if the user is playing in turn.  This is complicated because we
5321        let the user "pick up" a piece before it is his turn.  So the piece he
5322        tried to pick up may have been captured by the time he puts it down!
5323        Therefore we use the color the user is supposed to be playing in this
5324        test, not the color of the piece that is currently on the starting
5325        square---except in EditGame mode, where the user is playing both
5326        sides; fortunately there the capture race can't happen.  (It can
5327        now happen in IcsExamining mode, but that's just too bad.  The user
5328        will get a somewhat confusing message in that case.)
5329        */
5330
5331     switch (gameMode) {
5332       case PlayFromGameFile:
5333       case AnalyzeFile:
5334       case TwoMachinesPlay:
5335       case EndOfGame:
5336       case IcsObserving:
5337       case IcsIdle:
5338         /* We switched into a game mode where moves are not accepted,
5339            perhaps while the mouse button was down. */
5340         return ImpossibleMove;
5341
5342       case MachinePlaysWhite:
5343         /* User is moving for Black */
5344         if (WhiteOnMove(currentMove)) {
5345             DisplayMoveError(_("It is White's turn"));
5346             return ImpossibleMove;
5347         }
5348         break;
5349
5350       case MachinePlaysBlack:
5351         /* User is moving for White */
5352         if (!WhiteOnMove(currentMove)) {
5353             DisplayMoveError(_("It is Black's turn"));
5354             return ImpossibleMove;
5355         }
5356         break;
5357
5358       case EditGame:
5359       case IcsExamining:
5360       case BeginningOfGame:
5361       case AnalyzeMode:
5362       case Training:
5363         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5364             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5365             /* User is moving for Black */
5366             if (WhiteOnMove(currentMove)) {
5367                 DisplayMoveError(_("It is White's turn"));
5368                 return ImpossibleMove;
5369             }
5370         } else {
5371             /* User is moving for White */
5372             if (!WhiteOnMove(currentMove)) {
5373                 DisplayMoveError(_("It is Black's turn"));
5374                 return ImpossibleMove;
5375             }
5376         }
5377         break;
5378
5379       case IcsPlayingBlack:
5380         /* User is moving for Black */
5381         if (WhiteOnMove(currentMove)) {
5382             if (!appData.premove) {
5383                 DisplayMoveError(_("It is White's turn"));
5384             } else if (toX >= 0 && toY >= 0) {
5385                 premoveToX = toX;
5386                 premoveToY = toY;
5387                 premoveFromX = fromX;
5388                 premoveFromY = fromY;
5389                 premovePromoChar = promoChar;
5390                 gotPremove = 1;
5391                 if (appData.debugMode) 
5392                     fprintf(debugFP, "Got premove: fromX %d,"
5393                             "fromY %d, toX %d, toY %d\n",
5394                             fromX, fromY, toX, toY);
5395             }
5396             return ImpossibleMove;
5397         }
5398         break;
5399
5400       case IcsPlayingWhite:
5401         /* User is moving for White */
5402         if (!WhiteOnMove(currentMove)) {
5403             if (!appData.premove) {
5404                 DisplayMoveError(_("It is Black's turn"));
5405             } else if (toX >= 0 && toY >= 0) {
5406                 premoveToX = toX;
5407                 premoveToY = toY;
5408                 premoveFromX = fromX;
5409                 premoveFromY = fromY;
5410                 premovePromoChar = promoChar;
5411                 gotPremove = 1;
5412                 if (appData.debugMode) 
5413                     fprintf(debugFP, "Got premove: fromX %d,"
5414                             "fromY %d, toX %d, toY %d\n",
5415                             fromX, fromY, toX, toY);
5416             }
5417             return ImpossibleMove;
5418         }
5419         break;
5420
5421       default:
5422         break;
5423
5424       case EditPosition:
5425         /* EditPosition, empty square, or different color piece;
5426            click-click move is possible */
5427         if (toX == -2 || toY == -2) {
5428             boards[0][fromY][fromX] = EmptySquare;
5429             return AmbiguousMove;
5430         } else if (toX >= 0 && toY >= 0) {
5431             boards[0][toY][toX] = boards[0][fromY][fromX];
5432             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5433                 if(boards[0][fromY][0] != EmptySquare) {
5434                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5435                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5436                 }
5437             } else
5438             if(fromX == BOARD_RGHT+1) {
5439                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5440                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5441                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5442                 }
5443             } else
5444             boards[0][fromY][fromX] = EmptySquare;
5445             return AmbiguousMove;
5446         }
5447         return ImpossibleMove;
5448     }
5449
5450     if(toX < 0 || toY < 0) return ImpossibleMove;
5451     pdown = boards[currentMove][fromY][fromX];
5452     pup = boards[currentMove][toY][toX];
5453
5454     /* [HGM] If move started in holdings, it means a drop */
5455     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5456          if( pup != EmptySquare ) return ImpossibleMove;
5457          if(appData.testLegality) {
5458              /* it would be more logical if LegalityTest() also figured out
5459               * which drops are legal. For now we forbid pawns on back rank.
5460               * Shogi is on its own here...
5461               */
5462              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5463                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5464                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5465          }
5466          return WhiteDrop; /* Not needed to specify white or black yet */
5467     }
5468
5469     /* [HGM] always test for legality, to get promotion info */
5470     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5471                                          fromY, fromX, toY, toX, promoChar);
5472     /* [HGM] but possibly ignore an IllegalMove result */
5473     if (appData.testLegality) {
5474         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5475             DisplayMoveError(_("Illegal move"));
5476             return ImpossibleMove;
5477         }
5478     }
5479
5480     return moveType;
5481     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5482        function is made into one that returns an OK move type if FinishMove
5483        should be called. This to give the calling driver routine the
5484        opportunity to finish the userMove input with a promotion popup,
5485        without bothering the user with this for invalid or illegal moves */
5486
5487 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5488 }
5489
5490 /* Common tail of UserMoveEvent and DropMenuEvent */
5491 int
5492 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5493      ChessMove moveType;
5494      int fromX, fromY, toX, toY;
5495      /*char*/int promoChar;
5496 {
5497     char *bookHit = 0;
5498
5499     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5500         // [HGM] superchess: suppress promotions to non-available piece
5501         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5502         if(WhiteOnMove(currentMove)) {
5503             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5504         } else {
5505             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5506         }
5507     }
5508
5509     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5510        move type in caller when we know the move is a legal promotion */
5511     if(moveType == NormalMove && promoChar)
5512         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5513
5514     /* [HGM] convert drag-and-drop piece drops to standard form */
5515     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5516          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5517            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5518                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5519            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5520            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5521            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5522            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5523          fromY = DROP_RANK;
5524     }
5525
5526     /* [HGM] <popupFix> The following if has been moved here from
5527        UserMoveEvent(). Because it seemed to belong here (why not allow
5528        piece drops in training games?), and because it can only be
5529        performed after it is known to what we promote. */
5530     if (gameMode == Training) {
5531       /* compare the move played on the board to the next move in the
5532        * game. If they match, display the move and the opponent's response. 
5533        * If they don't match, display an error message.
5534        */
5535       int saveAnimate;
5536       Board testBoard;
5537       CopyBoard(testBoard, boards[currentMove]);
5538       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5539
5540       if (CompareBoards(testBoard, boards[currentMove+1])) {
5541         ForwardInner(currentMove+1);
5542
5543         /* Autoplay the opponent's response.
5544          * if appData.animate was TRUE when Training mode was entered,
5545          * the response will be animated.
5546          */
5547         saveAnimate = appData.animate;
5548         appData.animate = animateTraining;
5549         ForwardInner(currentMove+1);
5550         appData.animate = saveAnimate;
5551
5552         /* check for the end of the game */
5553         if (currentMove >= forwardMostMove) {
5554           gameMode = PlayFromGameFile;
5555           ModeHighlight();
5556           SetTrainingModeOff();
5557           DisplayInformation(_("End of game"));
5558         }
5559       } else {
5560         DisplayError(_("Incorrect move"), 0);
5561       }
5562       return 1;
5563     }
5564
5565   /* Ok, now we know that the move is good, so we can kill
5566      the previous line in Analysis Mode */
5567   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5568                                 && currentMove < forwardMostMove) {
5569     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5570   }
5571
5572   /* If we need the chess program but it's dead, restart it */
5573   ResurrectChessProgram();
5574
5575   /* A user move restarts a paused game*/
5576   if (pausing)
5577     PauseEvent();
5578
5579   thinkOutput[0] = NULLCHAR;
5580
5581   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5582
5583   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5584
5585   if (gameMode == BeginningOfGame) {
5586     if (appData.noChessProgram) {
5587       gameMode = EditGame;
5588       SetGameInfo();
5589     } else {
5590       char buf[MSG_SIZ];
5591       gameMode = MachinePlaysBlack;
5592       StartClocks();
5593       SetGameInfo();
5594       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5595       DisplayTitle(buf);
5596       if (first.sendName) {
5597         sprintf(buf, "name %s\n", gameInfo.white);
5598         SendToProgram(buf, &first);
5599       }
5600       StartClocks();
5601     }
5602     ModeHighlight();
5603   }
5604
5605   /* Relay move to ICS or chess engine */
5606   if (appData.icsActive) {
5607     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5608         gameMode == IcsExamining) {
5609       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5610         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5611         SendToICS("draw ");
5612         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5613       }
5614       // also send plain move, in case ICS does not understand atomic claims
5615       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5616       ics_user_moved = 1;
5617     }
5618   } else {
5619     if (first.sendTime && (gameMode == BeginningOfGame ||
5620                            gameMode == MachinePlaysWhite ||
5621                            gameMode == MachinePlaysBlack)) {
5622       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5623     }
5624     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5625          // [HGM] book: if program might be playing, let it use book
5626         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5627         first.maybeThinking = TRUE;
5628     } else SendMoveToProgram(forwardMostMove-1, &first);
5629     if (currentMove == cmailOldMove + 1) {
5630       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5631     }
5632   }
5633
5634   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5635
5636   switch (gameMode) {
5637   case EditGame:
5638     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5639     case MT_NONE:
5640     case MT_CHECK:
5641       break;
5642     case MT_CHECKMATE:
5643     case MT_STAINMATE:
5644       if (WhiteOnMove(currentMove)) {
5645         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5646       } else {
5647         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5648       }
5649       break;
5650     case MT_STALEMATE:
5651       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5652       break;
5653     }
5654     break;
5655     
5656   case MachinePlaysBlack:
5657   case MachinePlaysWhite:
5658     /* disable certain menu options while machine is thinking */
5659     SetMachineThinkingEnables();
5660     break;
5661
5662   default:
5663     break;
5664   }
5665
5666   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5667         
5668   if(bookHit) { // [HGM] book: simulate book reply
5669         static char bookMove[MSG_SIZ]; // a bit generous?
5670
5671         programStats.nodes = programStats.depth = programStats.time = 
5672         programStats.score = programStats.got_only_move = 0;
5673         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5674
5675         strcpy(bookMove, "move ");
5676         strcat(bookMove, bookHit);
5677         HandleMachineMove(bookMove, &first);
5678   }
5679   return 1;
5680 }
5681
5682 void
5683 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5684      int fromX, fromY, toX, toY;
5685      int promoChar;
5686 {
5687     /* [HGM] This routine was added to allow calling of its two logical
5688        parts from other modules in the old way. Before, UserMoveEvent()
5689        automatically called FinishMove() if the move was OK, and returned
5690        otherwise. I separated the two, in order to make it possible to
5691        slip a promotion popup in between. But that it always needs two
5692        calls, to the first part, (now called UserMoveTest() ), and to
5693        FinishMove if the first part succeeded. Calls that do not need
5694        to do anything in between, can call this routine the old way. 
5695     */
5696     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5697 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5698     if(moveType == AmbiguousMove)
5699         DrawPosition(FALSE, boards[currentMove]);
5700     else if(moveType != ImpossibleMove && moveType != Comment)
5701         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5702 }
5703
5704 void
5705 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5706      Board board;
5707      int flags;
5708      ChessMove kind;
5709      int rf, ff, rt, ft;
5710      VOIDSTAR closure;
5711 {
5712     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5713     Markers *m = (Markers *) closure;
5714     if(rf == fromY && ff == fromX)
5715         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5716                          || kind == WhiteCapturesEnPassant
5717                          || kind == BlackCapturesEnPassant);
5718     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5719 }
5720
5721 void
5722 MarkTargetSquares(int clear)
5723 {
5724   int x, y;
5725   if(!appData.markers || !appData.highlightDragging || 
5726      !appData.testLegality || gameMode == EditPosition) return;
5727   if(clear) {
5728     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5729   } else {
5730     int capt = 0;
5731     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5732     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5733       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5734       if(capt)
5735       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5736     }
5737   }
5738   DrawPosition(TRUE, NULL);
5739 }
5740
5741 void LeftClick(ClickType clickType, int xPix, int yPix)
5742 {
5743     int x, y;
5744     Boolean saveAnimate;
5745     static int second = 0, promotionChoice = 0;
5746     char promoChoice = NULLCHAR;
5747
5748     if (clickType == Press) ErrorPopDown();
5749     MarkTargetSquares(1);
5750
5751     x = EventToSquare(xPix, BOARD_WIDTH);
5752     y = EventToSquare(yPix, BOARD_HEIGHT);
5753     if (!flipView && y >= 0) {
5754         y = BOARD_HEIGHT - 1 - y;
5755     }
5756     if (flipView && x >= 0) {
5757         x = BOARD_WIDTH - 1 - x;
5758     }
5759
5760     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5761         if(clickType == Release) return; // ignore upclick of click-click destination
5762         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5763         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5764         if(gameInfo.holdingsWidth && 
5765                 (WhiteOnMove(currentMove) 
5766                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5767                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5768             // click in right holdings, for determining promotion piece
5769             ChessSquare p = boards[currentMove][y][x];
5770             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5771             if(p != EmptySquare) {
5772                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5773                 fromX = fromY = -1;
5774                 return;
5775             }
5776         }
5777         DrawPosition(FALSE, boards[currentMove]);
5778         return;
5779     }
5780
5781     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5782     if(clickType == Press
5783             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5784               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5785               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5786         return;
5787
5788     if (fromX == -1) {
5789         if (clickType == Press) {
5790             /* First square */
5791             if (OKToStartUserMove(x, y)) {
5792                 fromX = x;
5793                 fromY = y;
5794                 second = 0;
5795                 MarkTargetSquares(0);
5796                 DragPieceBegin(xPix, yPix);
5797                 if (appData.highlightDragging) {
5798                     SetHighlights(x, y, -1, -1);
5799                 }
5800             }
5801         }
5802         return;
5803     }
5804
5805     /* fromX != -1 */
5806     if (clickType == Press && gameMode != EditPosition) {
5807         ChessSquare fromP;
5808         ChessSquare toP;
5809         int frc;
5810
5811         // ignore off-board to clicks
5812         if(y < 0 || x < 0) return;
5813
5814         /* Check if clicking again on the same color piece */
5815         fromP = boards[currentMove][fromY][fromX];
5816         toP = boards[currentMove][y][x];
5817         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5818         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5819              WhitePawn <= toP && toP <= WhiteKing &&
5820              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5821              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5822             (BlackPawn <= fromP && fromP <= BlackKing && 
5823              BlackPawn <= toP && toP <= BlackKing &&
5824              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5825              !(fromP == BlackKing && toP == BlackRook && frc))) {
5826             /* Clicked again on same color piece -- changed his mind */
5827             second = (x == fromX && y == fromY);
5828             if (appData.highlightDragging) {
5829                 SetHighlights(x, y, -1, -1);
5830             } else {
5831                 ClearHighlights();
5832             }
5833             if (OKToStartUserMove(x, y)) {
5834                 fromX = x;
5835                 fromY = y;
5836                 MarkTargetSquares(0);
5837                 DragPieceBegin(xPix, yPix);
5838             }
5839             return;
5840         }
5841         // ignore clicks on holdings
5842         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5843     }
5844
5845     if (clickType == Release && x == fromX && y == fromY) {
5846         DragPieceEnd(xPix, yPix);
5847         if (appData.animateDragging) {
5848             /* Undo animation damage if any */
5849             DrawPosition(FALSE, NULL);
5850         }
5851         if (second) {
5852             /* Second up/down in same square; just abort move */
5853             second = 0;
5854             fromX = fromY = -1;
5855             ClearHighlights();
5856             gotPremove = 0;
5857             ClearPremoveHighlights();
5858         } else {
5859             /* First upclick in same square; start click-click mode */
5860             SetHighlights(x, y, -1, -1);
5861         }
5862         return;
5863     }
5864
5865     /* we now have a different from- and (possibly off-board) to-square */
5866     /* Completed move */
5867     toX = x;
5868     toY = y;
5869     saveAnimate = appData.animate;
5870     if (clickType == Press) {
5871         /* Finish clickclick move */
5872         if (appData.animate || appData.highlightLastMove) {
5873             SetHighlights(fromX, fromY, toX, toY);
5874         } else {
5875             ClearHighlights();
5876         }
5877     } else {
5878         /* Finish drag move */
5879         if (appData.highlightLastMove) {
5880             SetHighlights(fromX, fromY, toX, toY);
5881         } else {
5882             ClearHighlights();
5883         }
5884         DragPieceEnd(xPix, yPix);
5885         /* Don't animate move and drag both */
5886         appData.animate = FALSE;
5887     }
5888
5889     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5890     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5891         ChessSquare piece = boards[currentMove][fromY][fromX];
5892         if(gameMode == EditPosition && piece != EmptySquare &&
5893            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5894             int n;
5895              
5896             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5897                 n = PieceToNumber(piece - (int)BlackPawn);
5898                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5899                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5900                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5901             } else
5902             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5903                 n = PieceToNumber(piece);
5904                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5905                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5906                 boards[currentMove][n][BOARD_WIDTH-2]++;
5907             }
5908             boards[currentMove][fromY][fromX] = EmptySquare;
5909         }
5910         ClearHighlights();
5911         fromX = fromY = -1;
5912         DrawPosition(TRUE, boards[currentMove]);
5913         return;
5914     }
5915
5916     // off-board moves should not be highlighted
5917     if(x < 0 || x < 0) ClearHighlights();
5918
5919     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5920         SetHighlights(fromX, fromY, toX, toY);
5921         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5922             // [HGM] super: promotion to captured piece selected from holdings
5923             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5924             promotionChoice = TRUE;
5925             // kludge follows to temporarily execute move on display, without promoting yet
5926             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5927             boards[currentMove][toY][toX] = p;
5928             DrawPosition(FALSE, boards[currentMove]);
5929             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5930             boards[currentMove][toY][toX] = q;
5931             DisplayMessage("Click in holdings to choose piece", "");
5932             return;
5933         }
5934         PromotionPopUp();
5935     } else {
5936         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5937         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5938         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5939         fromX = fromY = -1;
5940     }
5941     appData.animate = saveAnimate;
5942     if (appData.animate || appData.animateDragging) {
5943         /* Undo animation damage if needed */
5944         DrawPosition(FALSE, NULL);
5945     }
5946 }
5947
5948 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
5949 {   // front-end-free part taken out of PieceMenuPopup
5950     int whichMenu; int xSqr, ySqr;
5951
5952     xSqr = EventToSquare(x, BOARD_WIDTH);
5953     ySqr = EventToSquare(y, BOARD_HEIGHT);
5954     if (action == Release) UnLoadPV(); // [HGM] pv
5955     if (action != Press) return -2;
5956     switch (gameMode) {
5957       case IcsExamining:
5958         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
5959       case EditPosition:
5960         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
5961         if (xSqr < 0 || ySqr < 0) return -1;\r
5962         whichMenu = 0; // edit-position menu
5963         break;
5964       case IcsObserving:
5965         if(!appData.icsEngineAnalyze) return -1;
5966       case IcsPlayingWhite:
5967       case IcsPlayingBlack:
5968         if(!appData.zippyPlay) goto noZip;
5969       case AnalyzeMode:
5970       case AnalyzeFile:
5971       case MachinePlaysWhite:
5972       case MachinePlaysBlack:
5973       case TwoMachinesPlay: // [HGM] pv: use for showing PV
5974         if (!appData.dropMenu) {
5975           LoadPV(x, y);
5976           return 2; // flag front-end to grab mouse events
5977         }
5978         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
5979            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
5980       case EditGame:
5981       noZip:
5982         if (xSqr < 0 || ySqr < 0) return -1;
5983         if (!appData.dropMenu || appData.testLegality &&
5984             gameInfo.variant != VariantBughouse &&
5985             gameInfo.variant != VariantCrazyhouse) return -1;
5986         whichMenu = 1; // drop menu
5987         break;
5988       default:
5989         return -1;
5990     }
5991
5992     if (((*fromX = xSqr) < 0) ||
5993         ((*fromY = ySqr) < 0)) {
5994         *fromX = *fromY = -1;
5995         return -1;
5996     }
5997     if (flipView)
5998       *fromX = BOARD_WIDTH - 1 - *fromX;
5999     else
6000       *fromY = BOARD_HEIGHT - 1 - *fromY;
6001
6002     return whichMenu;
6003 }
6004
6005 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6006 {
6007 //    char * hint = lastHint;
6008     FrontEndProgramStats stats;
6009
6010     stats.which = cps == &first ? 0 : 1;
6011     stats.depth = cpstats->depth;
6012     stats.nodes = cpstats->nodes;
6013     stats.score = cpstats->score;
6014     stats.time = cpstats->time;
6015     stats.pv = cpstats->movelist;
6016     stats.hint = lastHint;
6017     stats.an_move_index = 0;
6018     stats.an_move_count = 0;
6019
6020     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6021         stats.hint = cpstats->move_name;
6022         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6023         stats.an_move_count = cpstats->nr_moves;
6024     }
6025
6026     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6027
6028     SetProgramStats( &stats );
6029 }
6030
6031 int
6032 Adjudicate(ChessProgramState *cps)
6033 {       // [HGM] some adjudications useful with buggy engines
6034         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6035         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6036         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6037         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6038         int k, count = 0; static int bare = 1;
6039         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6040         Boolean canAdjudicate = !appData.icsActive;
6041
6042         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6043         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6044             if( appData.testLegality )
6045             {   /* [HGM] Some more adjudications for obstinate engines */
6046                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6047                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6048                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6049                 static int moveCount = 6;
6050                 ChessMove result;
6051                 char *reason = NULL;
6052
6053                 /* Count what is on board. */
6054                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6055                 {   ChessSquare p = boards[forwardMostMove][i][j];
6056                     int m=i;
6057
6058                     switch((int) p)
6059                     {   /* count B,N,R and other of each side */
6060                         case WhiteKing:
6061                         case BlackKing:
6062                              NrK++; break; // [HGM] atomic: count Kings
6063                         case WhiteKnight:
6064                              NrWN++; break;
6065                         case WhiteBishop:
6066                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6067                              bishopsColor |= 1 << ((i^j)&1);
6068                              NrWB++; break;
6069                         case BlackKnight:
6070                              NrBN++; break;
6071                         case BlackBishop:
6072                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6073                              bishopsColor |= 1 << ((i^j)&1);
6074                              NrBB++; break;
6075                         case WhiteRook:
6076                              NrWR++; break;
6077                         case BlackRook:
6078                              NrBR++; break;
6079                         case WhiteQueen:
6080                              NrWQ++; break;
6081                         case BlackQueen:
6082                              NrBQ++; break;
6083                         case EmptySquare: 
6084                              break;
6085                         case BlackPawn:
6086                              m = 7-i;
6087                         case WhitePawn:
6088                              PawnAdvance += m; NrPawns++;
6089                     }
6090                     NrPieces += (p != EmptySquare);
6091                     NrW += ((int)p < (int)BlackPawn);
6092                     if(gameInfo.variant == VariantXiangqi && 
6093                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6094                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6095                         NrW -= ((int)p < (int)BlackPawn);
6096                     }
6097                 }
6098
6099                 /* Some material-based adjudications that have to be made before stalemate test */
6100                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6101                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6102                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6103                      if(canAdjudicate && appData.checkMates) {
6104                          if(engineOpponent)
6105                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6106                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6107                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6108                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6109                          return 1;
6110                      }
6111                 }
6112
6113                 /* Bare King in Shatranj (loses) or Losers (wins) */
6114                 if( NrW == 1 || NrPieces - NrW == 1) {
6115                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6116                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6117                      if(canAdjudicate && appData.checkMates) {
6118                          if(engineOpponent)
6119                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6120                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6121                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6122                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6123                          return 1;
6124                      }
6125                   } else
6126                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6127                   {    /* bare King */
6128                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6129                         if(canAdjudicate && appData.checkMates) {
6130                             /* but only adjudicate if adjudication enabled */
6131                             if(engineOpponent)
6132                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6133                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6134                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6135                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6136                             return 1;
6137                         }
6138                   }
6139                 } else bare = 1;
6140
6141
6142             // don't wait for engine to announce game end if we can judge ourselves
6143             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6144               case MT_CHECK:
6145                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6146                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6147                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6148                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6149                             checkCnt++;
6150                         if(checkCnt >= 2) {
6151                             reason = "Xboard adjudication: 3rd check";
6152                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6153                             break;
6154                         }
6155                     }
6156                 }
6157               case MT_NONE:
6158               default:
6159                 break;
6160               case MT_STALEMATE:
6161               case MT_STAINMATE:
6162                 reason = "Xboard adjudication: Stalemate";
6163                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6164                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6165                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6166                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6167                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6168                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6169                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6170                                                                         EP_CHECKMATE : EP_WINS);
6171                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6172                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6173                 }
6174                 break;
6175               case MT_CHECKMATE:
6176                 reason = "Xboard adjudication: Checkmate";
6177                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6178                 break;
6179             }
6180
6181                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6182                     case EP_STALEMATE:
6183                         result = GameIsDrawn; break;
6184                     case EP_CHECKMATE:
6185                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6186                     case EP_WINS:
6187                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6188                     default:
6189                         result = (ChessMove) 0;
6190                 }
6191                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6192                     if(engineOpponent)
6193                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6194                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6195                     GameEnds( result, reason, GE_XBOARD );
6196                     return 1;
6197                 }
6198
6199                 /* Next absolutely insufficient mating material. */
6200                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6201                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6202                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6203                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6204                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6205
6206                      /* always flag draws, for judging claims */
6207                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6208
6209                      if(canAdjudicate && appData.materialDraws) {
6210                          /* but only adjudicate them if adjudication enabled */
6211                          if(engineOpponent) {
6212                            SendToProgram("force\n", engineOpponent); // suppress reply
6213                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6214                          }
6215                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6216                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6217                          return 1;
6218                      }
6219                 }
6220
6221                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6222                 if(NrPieces == 4 && 
6223                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6224                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6225                    || NrWN==2 || NrBN==2     /* KNNK */
6226                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6227                   ) ) {
6228                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6229                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6230                           if(engineOpponent) {
6231                             SendToProgram("force\n", engineOpponent); // suppress reply
6232                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6233                           }
6234                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6235                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6236                           return 1;
6237                      }
6238                 } else moveCount = 6;
6239             }
6240         }
6241           
6242         if (appData.debugMode) { int i;
6243             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6244                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6245                     appData.drawRepeats);
6246             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6247               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6248             
6249         }
6250
6251         // Repetition draws and 50-move rule can be applied independently of legality testing
6252
6253                 /* Check for rep-draws */
6254                 count = 0;
6255                 for(k = forwardMostMove-2;
6256                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6257                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6258                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6259                     k-=2)
6260                 {   int rights=0;
6261                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6262                         /* compare castling rights */
6263                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6264                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6265                                 rights++; /* King lost rights, while rook still had them */
6266                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6267                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6268                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6269                                    rights++; /* but at least one rook lost them */
6270                         }
6271                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6272                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6273                                 rights++; 
6274                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6275                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6276                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6277                                    rights++;
6278                         }
6279                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6280                             && appData.drawRepeats > 1) {
6281                              /* adjudicate after user-specified nr of repeats */
6282                              if(engineOpponent) {
6283                                SendToProgram("force\n", engineOpponent); // suppress reply
6284                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6285                              }
6286                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6287                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6288                                 // [HGM] xiangqi: check for forbidden perpetuals
6289                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6290                                 for(m=forwardMostMove; m>k; m-=2) {
6291                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6292                                         ourPerpetual = 0; // the current mover did not always check
6293                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6294                                         hisPerpetual = 0; // the opponent did not always check
6295                                 }
6296                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6297                                                                         ourPerpetual, hisPerpetual);
6298                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6299                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6300                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6301                                     return 1;
6302                                 }
6303                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6304                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6305                                 // Now check for perpetual chases
6306                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6307                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6308                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6309                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6310                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6311                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6312                                         return 1;
6313                                     }
6314                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6315                                         break; // Abort repetition-checking loop.
6316                                 }
6317                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6318                              }
6319                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6320                              return 1;
6321                         }
6322                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6323                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6324                     }
6325                 }
6326
6327                 /* Now we test for 50-move draws. Determine ply count */
6328                 count = forwardMostMove;
6329                 /* look for last irreversble move */
6330                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6331                     count--;
6332                 /* if we hit starting position, add initial plies */
6333                 if( count == backwardMostMove )
6334                     count -= initialRulePlies;
6335                 count = forwardMostMove - count; 
6336                 if( count >= 100)
6337                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6338                          /* this is used to judge if draw claims are legal */
6339                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6340                          if(engineOpponent) {
6341                            SendToProgram("force\n", engineOpponent); // suppress reply
6342                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6343                          }
6344                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6345                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6346                          return 1;
6347                 }
6348
6349                 /* if draw offer is pending, treat it as a draw claim
6350                  * when draw condition present, to allow engines a way to
6351                  * claim draws before making their move to avoid a race
6352                  * condition occurring after their move
6353                  */
6354                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6355                          char *p = NULL;
6356                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6357                              p = "Draw claim: 50-move rule";
6358                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6359                              p = "Draw claim: 3-fold repetition";
6360                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6361                              p = "Draw claim: insufficient mating material";
6362                          if( p != NULL && canAdjudicate) {
6363                              if(engineOpponent) {
6364                                SendToProgram("force\n", engineOpponent); // suppress reply
6365                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6366                              }
6367                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6368                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6369                              return 1;
6370                          }
6371                 }
6372
6373                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6374                     if(engineOpponent) {
6375                       SendToProgram("force\n", engineOpponent); // suppress reply
6376                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6377                     }
6378                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6379                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6380                     return 1;
6381                 }
6382         return 0;
6383 }
6384
6385 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6386 {   // [HGM] book: this routine intercepts moves to simulate book replies
6387     char *bookHit = NULL;
6388
6389     //first determine if the incoming move brings opponent into his book
6390     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6391         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6392     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6393     if(bookHit != NULL && !cps->bookSuspend) {
6394         // make sure opponent is not going to reply after receiving move to book position
6395         SendToProgram("force\n", cps);
6396         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6397     }
6398     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6399     // now arrange restart after book miss
6400     if(bookHit) {
6401         // after a book hit we never send 'go', and the code after the call to this routine
6402         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6403         char buf[MSG_SIZ];
6404         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6405         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6406         SendToProgram(buf, cps);
6407         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6408     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6409         SendToProgram("go\n", cps);
6410         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6411     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6412         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6413             SendToProgram("go\n", cps); 
6414         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6415     }
6416     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6417 }
6418
6419 char *savedMessage;
6420 ChessProgramState *savedState;
6421 void DeferredBookMove(void)
6422 {
6423         if(savedState->lastPing != savedState->lastPong)
6424                     ScheduleDelayedEvent(DeferredBookMove, 10);
6425         else
6426         HandleMachineMove(savedMessage, savedState);
6427 }
6428
6429 void
6430 HandleMachineMove(message, cps)
6431      char *message;
6432      ChessProgramState *cps;
6433 {
6434     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6435     char realname[MSG_SIZ];
6436     int fromX, fromY, toX, toY;
6437     ChessMove moveType;
6438     char promoChar;
6439     char *p;
6440     int machineWhite;
6441     char *bookHit;
6442
6443     cps->userError = 0;
6444
6445 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6446     /*
6447      * Kludge to ignore BEL characters
6448      */
6449     while (*message == '\007') message++;
6450
6451     /*
6452      * [HGM] engine debug message: ignore lines starting with '#' character
6453      */
6454     if(cps->debug && *message == '#') return;
6455
6456     /*
6457      * Look for book output
6458      */
6459     if (cps == &first && bookRequested) {
6460         if (message[0] == '\t' || message[0] == ' ') {
6461             /* Part of the book output is here; append it */
6462             strcat(bookOutput, message);
6463             strcat(bookOutput, "  \n");
6464             return;
6465         } else if (bookOutput[0] != NULLCHAR) {
6466             /* All of book output has arrived; display it */
6467             char *p = bookOutput;
6468             while (*p != NULLCHAR) {
6469                 if (*p == '\t') *p = ' ';
6470                 p++;
6471             }
6472             DisplayInformation(bookOutput);
6473             bookRequested = FALSE;
6474             /* Fall through to parse the current output */
6475         }
6476     }
6477
6478     /*
6479      * Look for machine move.
6480      */
6481     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6482         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6483     {
6484         /* This method is only useful on engines that support ping */
6485         if (cps->lastPing != cps->lastPong) {
6486           if (gameMode == BeginningOfGame) {
6487             /* Extra move from before last new; ignore */
6488             if (appData.debugMode) {
6489                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6490             }
6491           } else {
6492             if (appData.debugMode) {
6493                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6494                         cps->which, gameMode);
6495             }
6496
6497             SendToProgram("undo\n", cps);
6498           }
6499           return;
6500         }
6501
6502         switch (gameMode) {
6503           case BeginningOfGame:
6504             /* Extra move from before last reset; ignore */
6505             if (appData.debugMode) {
6506                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6507             }
6508             return;
6509
6510           case EndOfGame:
6511           case IcsIdle:
6512           default:
6513             /* Extra move after we tried to stop.  The mode test is
6514                not a reliable way of detecting this problem, but it's
6515                the best we can do on engines that don't support ping.
6516             */
6517             if (appData.debugMode) {
6518                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6519                         cps->which, gameMode);
6520             }
6521             SendToProgram("undo\n", cps);
6522             return;
6523
6524           case MachinePlaysWhite:
6525           case IcsPlayingWhite:
6526             machineWhite = TRUE;
6527             break;
6528
6529           case MachinePlaysBlack:
6530           case IcsPlayingBlack:
6531             machineWhite = FALSE;
6532             break;
6533
6534           case TwoMachinesPlay:
6535             machineWhite = (cps->twoMachinesColor[0] == 'w');
6536             break;
6537         }
6538         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6539             if (appData.debugMode) {
6540                 fprintf(debugFP,
6541                         "Ignoring move out of turn by %s, gameMode %d"
6542                         ", forwardMost %d\n",
6543                         cps->which, gameMode, forwardMostMove);
6544             }
6545             return;
6546         }
6547
6548     if (appData.debugMode) { int f = forwardMostMove;
6549         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6550                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6551                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6552     }
6553         if(cps->alphaRank) AlphaRank(machineMove, 4);
6554         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6555                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6556             /* Machine move could not be parsed; ignore it. */
6557             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6558                     machineMove, cps->which);
6559             DisplayError(buf1, 0);
6560             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6561                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6562             if (gameMode == TwoMachinesPlay) {
6563               GameEnds(machineWhite ? BlackWins : WhiteWins,
6564                        buf1, GE_XBOARD);
6565             }
6566             return;
6567         }
6568
6569         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6570         /* So we have to redo legality test with true e.p. status here,  */
6571         /* to make sure an illegal e.p. capture does not slip through,   */
6572         /* to cause a forfeit on a justified illegal-move complaint      */
6573         /* of the opponent.                                              */
6574         if( gameMode==TwoMachinesPlay && appData.testLegality
6575             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6576                                                               ) {
6577            ChessMove moveType;
6578            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6579                              fromY, fromX, toY, toX, promoChar);
6580             if (appData.debugMode) {
6581                 int i;
6582                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6583                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6584                 fprintf(debugFP, "castling rights\n");
6585             }
6586             if(moveType == IllegalMove) {
6587                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6588                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6589                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6590                            buf1, GE_XBOARD);
6591                 return;
6592            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6593            /* [HGM] Kludge to handle engines that send FRC-style castling
6594               when they shouldn't (like TSCP-Gothic) */
6595            switch(moveType) {
6596              case WhiteASideCastleFR:
6597              case BlackASideCastleFR:
6598                toX+=2;
6599                currentMoveString[2]++;
6600                break;
6601              case WhiteHSideCastleFR:
6602              case BlackHSideCastleFR:
6603                toX--;
6604                currentMoveString[2]--;
6605                break;
6606              default: ; // nothing to do, but suppresses warning of pedantic compilers
6607            }
6608         }
6609         hintRequested = FALSE;
6610         lastHint[0] = NULLCHAR;
6611         bookRequested = FALSE;
6612         /* Program may be pondering now */
6613         cps->maybeThinking = TRUE;
6614         if (cps->sendTime == 2) cps->sendTime = 1;
6615         if (cps->offeredDraw) cps->offeredDraw--;
6616
6617         /* currentMoveString is set as a side-effect of ParseOneMove */
6618         strcpy(machineMove, currentMoveString);
6619         strcat(machineMove, "\n");
6620         strcpy(moveList[forwardMostMove], machineMove);
6621
6622         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6623
6624         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6625         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6626             int count = 0;
6627
6628             while( count < adjudicateLossPlies ) {
6629                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6630
6631                 if( count & 1 ) {
6632                     score = -score; /* Flip score for winning side */
6633                 }
6634
6635                 if( score > adjudicateLossThreshold ) {
6636                     break;
6637                 }
6638
6639                 count++;
6640             }
6641
6642             if( count >= adjudicateLossPlies ) {
6643                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6644
6645                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6646                     "Xboard adjudication", 
6647                     GE_XBOARD );
6648
6649                 return;
6650             }
6651         }
6652
6653         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6654
6655 #if ZIPPY
6656         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6657             first.initDone) {
6658           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6659                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6660                 SendToICS("draw ");
6661                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6662           }
6663           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6664           ics_user_moved = 1;
6665           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6666                 char buf[3*MSG_SIZ];
6667
6668                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6669                         programStats.score / 100.,
6670                         programStats.depth,
6671                         programStats.time / 100.,
6672                         (unsigned int)programStats.nodes,
6673                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6674                         programStats.movelist);
6675                 SendToICS(buf);
6676 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6677           }
6678         }
6679 #endif
6680
6681         /* [AS] Save move info and clear stats for next move */
6682         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
6683         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
6684         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6685         ClearProgramStats();
6686         thinkOutput[0] = NULLCHAR;
6687         hiddenThinkOutputState = 0;
6688
6689         bookHit = NULL;
6690         if (gameMode == TwoMachinesPlay) {
6691             /* [HGM] relaying draw offers moved to after reception of move */
6692             /* and interpreting offer as claim if it brings draw condition */
6693             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6694                 SendToProgram("draw\n", cps->other);
6695             }
6696             if (cps->other->sendTime) {
6697                 SendTimeRemaining(cps->other,
6698                                   cps->other->twoMachinesColor[0] == 'w');
6699             }
6700             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6701             if (firstMove && !bookHit) {
6702                 firstMove = FALSE;
6703                 if (cps->other->useColors) {
6704                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6705                 }
6706                 SendToProgram("go\n", cps->other);
6707             }
6708             cps->other->maybeThinking = TRUE;
6709         }
6710
6711         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6712         
6713         if (!pausing && appData.ringBellAfterMoves) {
6714             RingBell();
6715         }
6716
6717         /* 
6718          * Reenable menu items that were disabled while
6719          * machine was thinking
6720          */
6721         if (gameMode != TwoMachinesPlay)
6722             SetUserThinkingEnables();
6723
6724         // [HGM] book: after book hit opponent has received move and is now in force mode
6725         // force the book reply into it, and then fake that it outputted this move by jumping
6726         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6727         if(bookHit) {
6728                 static char bookMove[MSG_SIZ]; // a bit generous?
6729
6730                 strcpy(bookMove, "move ");
6731                 strcat(bookMove, bookHit);
6732                 message = bookMove;
6733                 cps = cps->other;
6734                 programStats.nodes = programStats.depth = programStats.time = 
6735                 programStats.score = programStats.got_only_move = 0;
6736                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6737
6738                 if(cps->lastPing != cps->lastPong) {
6739                     savedMessage = message; // args for deferred call
6740                     savedState = cps;
6741                     ScheduleDelayedEvent(DeferredBookMove, 10);
6742                     return;
6743                 }
6744                 goto FakeBookMove;
6745         }
6746
6747         return;
6748     }
6749
6750     /* Set special modes for chess engines.  Later something general
6751      *  could be added here; for now there is just one kludge feature,
6752      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6753      *  when "xboard" is given as an interactive command.
6754      */
6755     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6756         cps->useSigint = FALSE;
6757         cps->useSigterm = FALSE;
6758     }
6759     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6760       ParseFeatures(message+8, cps);
6761       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6762     }
6763
6764     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6765      * want this, I was asked to put it in, and obliged.
6766      */
6767     if (!strncmp(message, "setboard ", 9)) {
6768         Board initial_position;
6769
6770         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6771
6772         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6773             DisplayError(_("Bad FEN received from engine"), 0);
6774             return ;
6775         } else {
6776            Reset(TRUE, FALSE);
6777            CopyBoard(boards[0], initial_position);
6778            initialRulePlies = FENrulePlies;
6779            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6780            else gameMode = MachinePlaysBlack;                 
6781            DrawPosition(FALSE, boards[currentMove]);
6782         }
6783         return;
6784     }
6785
6786     /*
6787      * Look for communication commands
6788      */
6789     if (!strncmp(message, "telluser ", 9)) {
6790         DisplayNote(message + 9);
6791         return;
6792     }
6793     if (!strncmp(message, "tellusererror ", 14)) {
6794         cps->userError = 1;
6795         DisplayError(message + 14, 0);
6796         return;
6797     }
6798     if (!strncmp(message, "tellopponent ", 13)) {
6799       if (appData.icsActive) {
6800         if (loggedOn) {
6801           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6802           SendToICS(buf1);
6803         }
6804       } else {
6805         DisplayNote(message + 13);
6806       }
6807       return;
6808     }
6809     if (!strncmp(message, "tellothers ", 11)) {
6810       if (appData.icsActive) {
6811         if (loggedOn) {
6812           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6813           SendToICS(buf1);
6814         }
6815       }
6816       return;
6817     }
6818     if (!strncmp(message, "tellall ", 8)) {
6819       if (appData.icsActive) {
6820         if (loggedOn) {
6821           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6822           SendToICS(buf1);
6823         }
6824       } else {
6825         DisplayNote(message + 8);
6826       }
6827       return;
6828     }
6829     if (strncmp(message, "warning", 7) == 0) {
6830         /* Undocumented feature, use tellusererror in new code */
6831         DisplayError(message, 0);
6832         return;
6833     }
6834     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6835         strcpy(realname, cps->tidy);
6836         strcat(realname, " query");
6837         AskQuestion(realname, buf2, buf1, cps->pr);
6838         return;
6839     }
6840     /* Commands from the engine directly to ICS.  We don't allow these to be 
6841      *  sent until we are logged on. Crafty kibitzes have been known to 
6842      *  interfere with the login process.
6843      */
6844     if (loggedOn) {
6845         if (!strncmp(message, "tellics ", 8)) {
6846             SendToICS(message + 8);
6847             SendToICS("\n");
6848             return;
6849         }
6850         if (!strncmp(message, "tellicsnoalias ", 15)) {
6851             SendToICS(ics_prefix);
6852             SendToICS(message + 15);
6853             SendToICS("\n");
6854             return;
6855         }
6856         /* The following are for backward compatibility only */
6857         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6858             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6859             SendToICS(ics_prefix);
6860             SendToICS(message);
6861             SendToICS("\n");
6862             return;
6863         }
6864     }
6865     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6866         return;
6867     }
6868     /*
6869      * If the move is illegal, cancel it and redraw the board.
6870      * Also deal with other error cases.  Matching is rather loose
6871      * here to accommodate engines written before the spec.
6872      */
6873     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6874         strncmp(message, "Error", 5) == 0) {
6875         if (StrStr(message, "name") || 
6876             StrStr(message, "rating") || StrStr(message, "?") ||
6877             StrStr(message, "result") || StrStr(message, "board") ||
6878             StrStr(message, "bk") || StrStr(message, "computer") ||
6879             StrStr(message, "variant") || StrStr(message, "hint") ||
6880             StrStr(message, "random") || StrStr(message, "depth") ||
6881             StrStr(message, "accepted")) {
6882             return;
6883         }
6884         if (StrStr(message, "protover")) {
6885           /* Program is responding to input, so it's apparently done
6886              initializing, and this error message indicates it is
6887              protocol version 1.  So we don't need to wait any longer
6888              for it to initialize and send feature commands. */
6889           FeatureDone(cps, 1);
6890           cps->protocolVersion = 1;
6891           return;
6892         }
6893         cps->maybeThinking = FALSE;
6894
6895         if (StrStr(message, "draw")) {
6896             /* Program doesn't have "draw" command */
6897             cps->sendDrawOffers = 0;
6898             return;
6899         }
6900         if (cps->sendTime != 1 &&
6901             (StrStr(message, "time") || StrStr(message, "otim"))) {
6902           /* Program apparently doesn't have "time" or "otim" command */
6903           cps->sendTime = 0;
6904           return;
6905         }
6906         if (StrStr(message, "analyze")) {
6907             cps->analysisSupport = FALSE;
6908             cps->analyzing = FALSE;
6909             Reset(FALSE, TRUE);
6910             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6911             DisplayError(buf2, 0);
6912             return;
6913         }
6914         if (StrStr(message, "(no matching move)st")) {
6915           /* Special kludge for GNU Chess 4 only */
6916           cps->stKludge = TRUE;
6917           SendTimeControl(cps, movesPerSession, timeControl,
6918                           timeIncrement, appData.searchDepth,
6919                           searchTime);
6920           return;
6921         }
6922         if (StrStr(message, "(no matching move)sd")) {
6923           /* Special kludge for GNU Chess 4 only */
6924           cps->sdKludge = TRUE;
6925           SendTimeControl(cps, movesPerSession, timeControl,
6926                           timeIncrement, appData.searchDepth,
6927                           searchTime);
6928           return;
6929         }
6930         if (!StrStr(message, "llegal")) {
6931             return;
6932         }
6933         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6934             gameMode == IcsIdle) return;
6935         if (forwardMostMove <= backwardMostMove) return;
6936         if (pausing) PauseEvent();
6937       if(appData.forceIllegal) {
6938             // [HGM] illegal: machine refused move; force position after move into it
6939           SendToProgram("force\n", cps);
6940           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6941                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6942                 // when black is to move, while there might be nothing on a2 or black
6943                 // might already have the move. So send the board as if white has the move.
6944                 // But first we must change the stm of the engine, as it refused the last move
6945                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6946                 if(WhiteOnMove(forwardMostMove)) {
6947                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6948                     SendBoard(cps, forwardMostMove); // kludgeless board
6949                 } else {
6950                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6951                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6952                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6953                 }
6954           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6955             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6956                  gameMode == TwoMachinesPlay)
6957               SendToProgram("go\n", cps);
6958             return;
6959       } else
6960         if (gameMode == PlayFromGameFile) {
6961             /* Stop reading this game file */
6962             gameMode = EditGame;
6963             ModeHighlight();
6964         }
6965         currentMove = --forwardMostMove;
6966         DisplayMove(currentMove-1); /* before DisplayMoveError */
6967         SwitchClocks();
6968         DisplayBothClocks();
6969         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6970                 parseList[currentMove], cps->which);
6971         DisplayMoveError(buf1);
6972         DrawPosition(FALSE, boards[currentMove]);
6973
6974         /* [HGM] illegal-move claim should forfeit game when Xboard */
6975         /* only passes fully legal moves                            */
6976         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6977             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6978                                 "False illegal-move claim", GE_XBOARD );
6979         }
6980         return;
6981     }
6982     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6983         /* Program has a broken "time" command that
6984            outputs a string not ending in newline.
6985            Don't use it. */
6986         cps->sendTime = 0;
6987     }
6988     
6989     /*
6990      * If chess program startup fails, exit with an error message.
6991      * Attempts to recover here are futile.
6992      */
6993     if ((StrStr(message, "unknown host") != NULL)
6994         || (StrStr(message, "No remote directory") != NULL)
6995         || (StrStr(message, "not found") != NULL)
6996         || (StrStr(message, "No such file") != NULL)
6997         || (StrStr(message, "can't alloc") != NULL)
6998         || (StrStr(message, "Permission denied") != NULL)) {
6999
7000         cps->maybeThinking = FALSE;
7001         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7002                 cps->which, cps->program, cps->host, message);
7003         RemoveInputSource(cps->isr);
7004         DisplayFatalError(buf1, 0, 1);
7005         return;
7006     }
7007     
7008     /* 
7009      * Look for hint output
7010      */
7011     if (sscanf(message, "Hint: %s", buf1) == 1) {
7012         if (cps == &first && hintRequested) {
7013             hintRequested = FALSE;
7014             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7015                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7016                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7017                                     PosFlags(forwardMostMove),
7018                                     fromY, fromX, toY, toX, promoChar, buf1);
7019                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7020                 DisplayInformation(buf2);
7021             } else {
7022                 /* Hint move could not be parsed!? */
7023               snprintf(buf2, sizeof(buf2),
7024                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7025                         buf1, cps->which);
7026                 DisplayError(buf2, 0);
7027             }
7028         } else {
7029             strcpy(lastHint, buf1);
7030         }
7031         return;
7032     }
7033
7034     /*
7035      * Ignore other messages if game is not in progress
7036      */
7037     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7038         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7039
7040     /*
7041      * look for win, lose, draw, or draw offer
7042      */
7043     if (strncmp(message, "1-0", 3) == 0) {
7044         char *p, *q, *r = "";
7045         p = strchr(message, '{');
7046         if (p) {
7047             q = strchr(p, '}');
7048             if (q) {
7049                 *q = NULLCHAR;
7050                 r = p + 1;
7051             }
7052         }
7053         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7054         return;
7055     } else if (strncmp(message, "0-1", 3) == 0) {
7056         char *p, *q, *r = "";
7057         p = strchr(message, '{');
7058         if (p) {
7059             q = strchr(p, '}');
7060             if (q) {
7061                 *q = NULLCHAR;
7062                 r = p + 1;
7063             }
7064         }
7065         /* Kludge for Arasan 4.1 bug */
7066         if (strcmp(r, "Black resigns") == 0) {
7067             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7068             return;
7069         }
7070         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7071         return;
7072     } else if (strncmp(message, "1/2", 3) == 0) {
7073         char *p, *q, *r = "";
7074         p = strchr(message, '{');
7075         if (p) {
7076             q = strchr(p, '}');
7077             if (q) {
7078                 *q = NULLCHAR;
7079                 r = p + 1;
7080             }
7081         }
7082             
7083         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7084         return;
7085
7086     } else if (strncmp(message, "White resign", 12) == 0) {
7087         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7088         return;
7089     } else if (strncmp(message, "Black resign", 12) == 0) {
7090         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7091         return;
7092     } else if (strncmp(message, "White matches", 13) == 0 ||
7093                strncmp(message, "Black matches", 13) == 0   ) {
7094         /* [HGM] ignore GNUShogi noises */
7095         return;
7096     } else if (strncmp(message, "White", 5) == 0 &&
7097                message[5] != '(' &&
7098                StrStr(message, "Black") == NULL) {
7099         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7100         return;
7101     } else if (strncmp(message, "Black", 5) == 0 &&
7102                message[5] != '(') {
7103         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7104         return;
7105     } else if (strcmp(message, "resign") == 0 ||
7106                strcmp(message, "computer resigns") == 0) {
7107         switch (gameMode) {
7108           case MachinePlaysBlack:
7109           case IcsPlayingBlack:
7110             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7111             break;
7112           case MachinePlaysWhite:
7113           case IcsPlayingWhite:
7114             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7115             break;
7116           case TwoMachinesPlay:
7117             if (cps->twoMachinesColor[0] == 'w')
7118               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7119             else
7120               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7121             break;
7122           default:
7123             /* can't happen */
7124             break;
7125         }
7126         return;
7127     } else if (strncmp(message, "opponent mates", 14) == 0) {
7128         switch (gameMode) {
7129           case MachinePlaysBlack:
7130           case IcsPlayingBlack:
7131             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7132             break;
7133           case MachinePlaysWhite:
7134           case IcsPlayingWhite:
7135             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7136             break;
7137           case TwoMachinesPlay:
7138             if (cps->twoMachinesColor[0] == 'w')
7139               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7140             else
7141               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7142             break;
7143           default:
7144             /* can't happen */
7145             break;
7146         }
7147         return;
7148     } else if (strncmp(message, "computer mates", 14) == 0) {
7149         switch (gameMode) {
7150           case MachinePlaysBlack:
7151           case IcsPlayingBlack:
7152             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7153             break;
7154           case MachinePlaysWhite:
7155           case IcsPlayingWhite:
7156             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7157             break;
7158           case TwoMachinesPlay:
7159             if (cps->twoMachinesColor[0] == 'w')
7160               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7161             else
7162               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7163             break;
7164           default:
7165             /* can't happen */
7166             break;
7167         }
7168         return;
7169     } else if (strncmp(message, "checkmate", 9) == 0) {
7170         if (WhiteOnMove(forwardMostMove)) {
7171             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7172         } else {
7173             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7174         }
7175         return;
7176     } else if (strstr(message, "Draw") != NULL ||
7177                strstr(message, "game is a draw") != NULL) {
7178         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7179         return;
7180     } else if (strstr(message, "offer") != NULL &&
7181                strstr(message, "draw") != NULL) {
7182 #if ZIPPY
7183         if (appData.zippyPlay && first.initDone) {
7184             /* Relay offer to ICS */
7185             SendToICS(ics_prefix);
7186             SendToICS("draw\n");
7187         }
7188 #endif
7189         cps->offeredDraw = 2; /* valid until this engine moves twice */
7190         if (gameMode == TwoMachinesPlay) {
7191             if (cps->other->offeredDraw) {
7192                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7193             /* [HGM] in two-machine mode we delay relaying draw offer      */
7194             /* until after we also have move, to see if it is really claim */
7195             }
7196         } else if (gameMode == MachinePlaysWhite ||
7197                    gameMode == MachinePlaysBlack) {
7198           if (userOfferedDraw) {
7199             DisplayInformation(_("Machine accepts your draw offer"));
7200             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7201           } else {
7202             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7203           }
7204         }
7205     }
7206
7207     
7208     /*
7209      * Look for thinking output
7210      */
7211     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7212           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7213                                 ) {
7214         int plylev, mvleft, mvtot, curscore, time;
7215         char mvname[MOVE_LEN];
7216         u64 nodes; // [DM]
7217         char plyext;
7218         int ignore = FALSE;
7219         int prefixHint = FALSE;
7220         mvname[0] = NULLCHAR;
7221
7222         switch (gameMode) {
7223           case MachinePlaysBlack:
7224           case IcsPlayingBlack:
7225             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7226             break;
7227           case MachinePlaysWhite:
7228           case IcsPlayingWhite:
7229             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7230             break;
7231           case AnalyzeMode:
7232           case AnalyzeFile:
7233             break;
7234           case IcsObserving: /* [DM] icsEngineAnalyze */
7235             if (!appData.icsEngineAnalyze) ignore = TRUE;
7236             break;
7237           case TwoMachinesPlay:
7238             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7239                 ignore = TRUE;
7240             }
7241             break;
7242           default:
7243             ignore = TRUE;
7244             break;
7245         }
7246
7247         if (!ignore) {
7248             buf1[0] = NULLCHAR;
7249             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7250                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7251
7252                 if (plyext != ' ' && plyext != '\t') {
7253                     time *= 100;
7254                 }
7255
7256                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7257                 if( cps->scoreIsAbsolute && 
7258                     ( gameMode == MachinePlaysBlack ||
7259                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7260                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7261                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7262                      !WhiteOnMove(currentMove)
7263                     ) )
7264                 {
7265                     curscore = -curscore;
7266                 }
7267
7268
7269                 programStats.depth = plylev;
7270                 programStats.nodes = nodes;
7271                 programStats.time = time;
7272                 programStats.score = curscore;
7273                 programStats.got_only_move = 0;
7274
7275                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7276                         int ticklen;
7277
7278                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7279                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7280                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7281                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7282                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7283                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7284                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7285                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7286                 }
7287
7288                 /* Buffer overflow protection */
7289                 if (buf1[0] != NULLCHAR) {
7290                     if (strlen(buf1) >= sizeof(programStats.movelist)
7291                         && appData.debugMode) {
7292                         fprintf(debugFP,
7293                                 "PV is too long; using the first %u bytes.\n",
7294                                 (unsigned) sizeof(programStats.movelist) - 1);
7295                     }
7296
7297                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7298                 } else {
7299                     sprintf(programStats.movelist, " no PV\n");
7300                 }
7301
7302                 if (programStats.seen_stat) {
7303                     programStats.ok_to_send = 1;
7304                 }
7305
7306                 if (strchr(programStats.movelist, '(') != NULL) {
7307                     programStats.line_is_book = 1;
7308                     programStats.nr_moves = 0;
7309                     programStats.moves_left = 0;
7310                 } else {
7311                     programStats.line_is_book = 0;
7312                 }
7313
7314                 SendProgramStatsToFrontend( cps, &programStats );
7315
7316                 /* 
7317                     [AS] Protect the thinkOutput buffer from overflow... this
7318                     is only useful if buf1 hasn't overflowed first!
7319                 */
7320                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7321                         plylev, 
7322                         (gameMode == TwoMachinesPlay ?
7323                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7324                         ((double) curscore) / 100.0,
7325                         prefixHint ? lastHint : "",
7326                         prefixHint ? " " : "" );
7327
7328                 if( buf1[0] != NULLCHAR ) {
7329                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7330
7331                     if( strlen(buf1) > max_len ) {
7332                         if( appData.debugMode) {
7333                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7334                         }
7335                         buf1[max_len+1] = '\0';
7336                     }
7337
7338                     strcat( thinkOutput, buf1 );
7339                 }
7340
7341                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7342                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7343                     DisplayMove(currentMove - 1);
7344                 }
7345                 return;
7346
7347             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7348                 /* crafty (9.25+) says "(only move) <move>"
7349                  * if there is only 1 legal move
7350                  */
7351                 sscanf(p, "(only move) %s", buf1);
7352                 sprintf(thinkOutput, "%s (only move)", buf1);
7353                 sprintf(programStats.movelist, "%s (only move)", buf1);
7354                 programStats.depth = 1;
7355                 programStats.nr_moves = 1;
7356                 programStats.moves_left = 1;
7357                 programStats.nodes = 1;
7358                 programStats.time = 1;
7359                 programStats.got_only_move = 1;
7360
7361                 /* Not really, but we also use this member to
7362                    mean "line isn't going to change" (Crafty
7363                    isn't searching, so stats won't change) */
7364                 programStats.line_is_book = 1;
7365
7366                 SendProgramStatsToFrontend( cps, &programStats );
7367                 
7368                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7369                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7370                     DisplayMove(currentMove - 1);
7371                 }
7372                 return;
7373             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7374                               &time, &nodes, &plylev, &mvleft,
7375                               &mvtot, mvname) >= 5) {
7376                 /* The stat01: line is from Crafty (9.29+) in response
7377                    to the "." command */
7378                 programStats.seen_stat = 1;
7379                 cps->maybeThinking = TRUE;
7380
7381                 if (programStats.got_only_move || !appData.periodicUpdates)
7382                   return;
7383
7384                 programStats.depth = plylev;
7385                 programStats.time = time;
7386                 programStats.nodes = nodes;
7387                 programStats.moves_left = mvleft;
7388                 programStats.nr_moves = mvtot;
7389                 strcpy(programStats.move_name, mvname);
7390                 programStats.ok_to_send = 1;
7391                 programStats.movelist[0] = '\0';
7392
7393                 SendProgramStatsToFrontend( cps, &programStats );
7394
7395                 return;
7396
7397             } else if (strncmp(message,"++",2) == 0) {
7398                 /* Crafty 9.29+ outputs this */
7399                 programStats.got_fail = 2;
7400                 return;
7401
7402             } else if (strncmp(message,"--",2) == 0) {
7403                 /* Crafty 9.29+ outputs this */
7404                 programStats.got_fail = 1;
7405                 return;
7406
7407             } else if (thinkOutput[0] != NULLCHAR &&
7408                        strncmp(message, "    ", 4) == 0) {
7409                 unsigned message_len;
7410
7411                 p = message;
7412                 while (*p && *p == ' ') p++;
7413
7414                 message_len = strlen( p );
7415
7416                 /* [AS] Avoid buffer overflow */
7417                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7418                     strcat(thinkOutput, " ");
7419                     strcat(thinkOutput, p);
7420                 }
7421
7422                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7423                     strcat(programStats.movelist, " ");
7424                     strcat(programStats.movelist, p);
7425                 }
7426
7427                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7428                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7429                     DisplayMove(currentMove - 1);
7430                 }
7431                 return;
7432             }
7433         }
7434         else {
7435             buf1[0] = NULLCHAR;
7436
7437             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7438                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7439             {
7440                 ChessProgramStats cpstats;
7441
7442                 if (plyext != ' ' && plyext != '\t') {
7443                     time *= 100;
7444                 }
7445
7446                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7447                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7448                     curscore = -curscore;
7449                 }
7450
7451                 cpstats.depth = plylev;
7452                 cpstats.nodes = nodes;
7453                 cpstats.time = time;
7454                 cpstats.score = curscore;
7455                 cpstats.got_only_move = 0;
7456                 cpstats.movelist[0] = '\0';
7457
7458                 if (buf1[0] != NULLCHAR) {
7459                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7460                 }
7461
7462                 cpstats.ok_to_send = 0;
7463                 cpstats.line_is_book = 0;
7464                 cpstats.nr_moves = 0;
7465                 cpstats.moves_left = 0;
7466
7467                 SendProgramStatsToFrontend( cps, &cpstats );
7468             }
7469         }
7470     }
7471 }
7472
7473
7474 /* Parse a game score from the character string "game", and
7475    record it as the history of the current game.  The game
7476    score is NOT assumed to start from the standard position. 
7477    The display is not updated in any way.
7478    */
7479 void
7480 ParseGameHistory(game)
7481      char *game;
7482 {
7483     ChessMove moveType;
7484     int fromX, fromY, toX, toY, boardIndex;
7485     char promoChar;
7486     char *p, *q;
7487     char buf[MSG_SIZ];
7488
7489     if (appData.debugMode)
7490       fprintf(debugFP, "Parsing game history: %s\n", game);
7491
7492     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7493     gameInfo.site = StrSave(appData.icsHost);
7494     gameInfo.date = PGNDate();
7495     gameInfo.round = StrSave("-");
7496
7497     /* Parse out names of players */
7498     while (*game == ' ') game++;
7499     p = buf;
7500     while (*game != ' ') *p++ = *game++;
7501     *p = NULLCHAR;
7502     gameInfo.white = StrSave(buf);
7503     while (*game == ' ') game++;
7504     p = buf;
7505     while (*game != ' ' && *game != '\n') *p++ = *game++;
7506     *p = NULLCHAR;
7507     gameInfo.black = StrSave(buf);
7508
7509     /* Parse moves */
7510     boardIndex = blackPlaysFirst ? 1 : 0;
7511     yynewstr(game);
7512     for (;;) {
7513         yyboardindex = boardIndex;
7514         moveType = (ChessMove) yylex();
7515         switch (moveType) {
7516           case IllegalMove:             /* maybe suicide chess, etc. */
7517   if (appData.debugMode) {
7518     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7519     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7520     setbuf(debugFP, NULL);
7521   }
7522           case WhitePromotionChancellor:
7523           case BlackPromotionChancellor:
7524           case WhitePromotionArchbishop:
7525           case BlackPromotionArchbishop:
7526           case WhitePromotionQueen:
7527           case BlackPromotionQueen:
7528           case WhitePromotionRook:
7529           case BlackPromotionRook:
7530           case WhitePromotionBishop:
7531           case BlackPromotionBishop:
7532           case WhitePromotionKnight:
7533           case BlackPromotionKnight:
7534           case WhitePromotionKing:
7535           case BlackPromotionKing:
7536           case NormalMove:
7537           case WhiteCapturesEnPassant:
7538           case BlackCapturesEnPassant:
7539           case WhiteKingSideCastle:
7540           case WhiteQueenSideCastle:
7541           case BlackKingSideCastle:
7542           case BlackQueenSideCastle:
7543           case WhiteKingSideCastleWild:
7544           case WhiteQueenSideCastleWild:
7545           case BlackKingSideCastleWild:
7546           case BlackQueenSideCastleWild:
7547           /* PUSH Fabien */
7548           case WhiteHSideCastleFR:
7549           case WhiteASideCastleFR:
7550           case BlackHSideCastleFR:
7551           case BlackASideCastleFR:
7552           /* POP Fabien */
7553             fromX = currentMoveString[0] - AAA;
7554             fromY = currentMoveString[1] - ONE;
7555             toX = currentMoveString[2] - AAA;
7556             toY = currentMoveString[3] - ONE;
7557             promoChar = currentMoveString[4];
7558             break;
7559           case WhiteDrop:
7560           case BlackDrop:
7561             fromX = moveType == WhiteDrop ?
7562               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7563             (int) CharToPiece(ToLower(currentMoveString[0]));
7564             fromY = DROP_RANK;
7565             toX = currentMoveString[2] - AAA;
7566             toY = currentMoveString[3] - ONE;
7567             promoChar = NULLCHAR;
7568             break;
7569           case AmbiguousMove:
7570             /* bug? */
7571             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7572   if (appData.debugMode) {
7573     fprintf(debugFP, "Ambiguous 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 ImpossibleMove:
7580             /* bug? */
7581             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7582   if (appData.debugMode) {
7583     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7584     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7585     setbuf(debugFP, NULL);
7586   }
7587             DisplayError(buf, 0);
7588             return;
7589           case (ChessMove) 0:   /* end of file */
7590             if (boardIndex < backwardMostMove) {
7591                 /* Oops, gap.  How did that happen? */
7592                 DisplayError(_("Gap in move list"), 0);
7593                 return;
7594             }
7595             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7596             if (boardIndex > forwardMostMove) {
7597                 forwardMostMove = boardIndex;
7598             }
7599             return;
7600           case ElapsedTime:
7601             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7602                 strcat(parseList[boardIndex-1], " ");
7603                 strcat(parseList[boardIndex-1], yy_text);
7604             }
7605             continue;
7606           case Comment:
7607           case PGNTag:
7608           case NAG:
7609           default:
7610             /* ignore */
7611             continue;
7612           case WhiteWins:
7613           case BlackWins:
7614           case GameIsDrawn:
7615           case GameUnfinished:
7616             if (gameMode == IcsExamining) {
7617                 if (boardIndex < backwardMostMove) {
7618                     /* Oops, gap.  How did that happen? */
7619                     return;
7620                 }
7621                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7622                 return;
7623             }
7624             gameInfo.result = moveType;
7625             p = strchr(yy_text, '{');
7626             if (p == NULL) p = strchr(yy_text, '(');
7627             if (p == NULL) {
7628                 p = yy_text;
7629                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7630             } else {
7631                 q = strchr(p, *p == '{' ? '}' : ')');
7632                 if (q != NULL) *q = NULLCHAR;
7633                 p++;
7634             }
7635             gameInfo.resultDetails = StrSave(p);
7636             continue;
7637         }
7638         if (boardIndex >= forwardMostMove &&
7639             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7640             backwardMostMove = blackPlaysFirst ? 1 : 0;
7641             return;
7642         }
7643         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7644                                  fromY, fromX, toY, toX, promoChar,
7645                                  parseList[boardIndex]);
7646         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7647         /* currentMoveString is set as a side-effect of yylex */
7648         strcpy(moveList[boardIndex], currentMoveString);
7649         strcat(moveList[boardIndex], "\n");
7650         boardIndex++;
7651         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7652         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7653           case MT_NONE:
7654           case MT_STALEMATE:
7655           default:
7656             break;
7657           case MT_CHECK:
7658             if(gameInfo.variant != VariantShogi)
7659                 strcat(parseList[boardIndex - 1], "+");
7660             break;
7661           case MT_CHECKMATE:
7662           case MT_STAINMATE:
7663             strcat(parseList[boardIndex - 1], "#");
7664             break;
7665         }
7666     }
7667 }
7668
7669
7670 /* Apply a move to the given board  */
7671 void
7672 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7673      int fromX, fromY, toX, toY;
7674      int promoChar;
7675      Board board;
7676 {
7677   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7678   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7679
7680     /* [HGM] compute & store e.p. status and castling rights for new position */
7681     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7682     { int i;
7683
7684       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7685       oldEP = (signed char)board[EP_STATUS];
7686       board[EP_STATUS] = EP_NONE;
7687
7688       if( board[toY][toX] != EmptySquare ) 
7689            board[EP_STATUS] = EP_CAPTURE;  
7690
7691       if( board[fromY][fromX] == WhitePawn ) {
7692            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7693                board[EP_STATUS] = EP_PAWN_MOVE;
7694            if( toY-fromY==2) {
7695                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7696                         gameInfo.variant != VariantBerolina || toX < fromX)
7697                       board[EP_STATUS] = toX | berolina;
7698                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7699                         gameInfo.variant != VariantBerolina || toX > fromX) 
7700                       board[EP_STATUS] = toX;
7701            }
7702       } else 
7703       if( board[fromY][fromX] == BlackPawn ) {
7704            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7705                board[EP_STATUS] = EP_PAWN_MOVE; 
7706            if( toY-fromY== -2) {
7707                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7708                         gameInfo.variant != VariantBerolina || toX < fromX)
7709                       board[EP_STATUS] = toX | berolina;
7710                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7711                         gameInfo.variant != VariantBerolina || toX > fromX) 
7712                       board[EP_STATUS] = toX;
7713            }
7714        }
7715
7716        for(i=0; i<nrCastlingRights; i++) {
7717            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7718               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7719              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7720        }
7721
7722     }
7723
7724   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7725   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7726        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7727          
7728   if (fromX == toX && fromY == toY) return;
7729
7730   if (fromY == DROP_RANK) {
7731         /* must be first */
7732         piece = board[toY][toX] = (ChessSquare) fromX;
7733   } else {
7734      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7735      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7736      if(gameInfo.variant == VariantKnightmate)
7737          king += (int) WhiteUnicorn - (int) WhiteKing;
7738
7739     /* Code added by Tord: */
7740     /* FRC castling assumed when king captures friendly rook. */
7741     if (board[fromY][fromX] == WhiteKing &&
7742              board[toY][toX] == WhiteRook) {
7743       board[fromY][fromX] = EmptySquare;
7744       board[toY][toX] = EmptySquare;
7745       if(toX > fromX) {
7746         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7747       } else {
7748         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7749       }
7750     } else if (board[fromY][fromX] == BlackKing &&
7751                board[toY][toX] == BlackRook) {
7752       board[fromY][fromX] = EmptySquare;
7753       board[toY][toX] = EmptySquare;
7754       if(toX > fromX) {
7755         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7756       } else {
7757         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7758       }
7759     /* End of code added by Tord */
7760
7761     } else if (board[fromY][fromX] == king
7762         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7763         && toY == fromY && toX > fromX+1) {
7764         board[fromY][fromX] = EmptySquare;
7765         board[toY][toX] = king;
7766         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7767         board[fromY][BOARD_RGHT-1] = EmptySquare;
7768     } else if (board[fromY][fromX] == king
7769         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7770                && toY == fromY && toX < fromX-1) {
7771         board[fromY][fromX] = EmptySquare;
7772         board[toY][toX] = king;
7773         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7774         board[fromY][BOARD_LEFT] = EmptySquare;
7775     } else if (board[fromY][fromX] == WhitePawn
7776                && toY >= BOARD_HEIGHT-promoRank
7777                && gameInfo.variant != VariantXiangqi
7778                ) {
7779         /* white pawn promotion */
7780         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7781         if (board[toY][toX] == EmptySquare) {
7782             board[toY][toX] = WhiteQueen;
7783         }
7784         if(gameInfo.variant==VariantBughouse ||
7785            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7786             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7787         board[fromY][fromX] = EmptySquare;
7788     } else if ((fromY == BOARD_HEIGHT-4)
7789                && (toX != fromX)
7790                && gameInfo.variant != VariantXiangqi
7791                && gameInfo.variant != VariantBerolina
7792                && (board[fromY][fromX] == WhitePawn)
7793                && (board[toY][toX] == EmptySquare)) {
7794         board[fromY][fromX] = EmptySquare;
7795         board[toY][toX] = WhitePawn;
7796         captured = board[toY - 1][toX];
7797         board[toY - 1][toX] = EmptySquare;
7798     } else if ((fromY == BOARD_HEIGHT-4)
7799                && (toX == fromX)
7800                && gameInfo.variant == VariantBerolina
7801                && (board[fromY][fromX] == WhitePawn)
7802                && (board[toY][toX] == EmptySquare)) {
7803         board[fromY][fromX] = EmptySquare;
7804         board[toY][toX] = WhitePawn;
7805         if(oldEP & EP_BEROLIN_A) {
7806                 captured = board[fromY][fromX-1];
7807                 board[fromY][fromX-1] = EmptySquare;
7808         }else{  captured = board[fromY][fromX+1];
7809                 board[fromY][fromX+1] = EmptySquare;
7810         }
7811     } else if (board[fromY][fromX] == king
7812         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7813                && toY == fromY && toX > fromX+1) {
7814         board[fromY][fromX] = EmptySquare;
7815         board[toY][toX] = king;
7816         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7817         board[fromY][BOARD_RGHT-1] = EmptySquare;
7818     } else if (board[fromY][fromX] == king
7819         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7820                && toY == fromY && toX < fromX-1) {
7821         board[fromY][fromX] = EmptySquare;
7822         board[toY][toX] = king;
7823         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7824         board[fromY][BOARD_LEFT] = EmptySquare;
7825     } else if (fromY == 7 && fromX == 3
7826                && board[fromY][fromX] == BlackKing
7827                && toY == 7 && toX == 5) {
7828         board[fromY][fromX] = EmptySquare;
7829         board[toY][toX] = BlackKing;
7830         board[fromY][7] = EmptySquare;
7831         board[toY][4] = BlackRook;
7832     } else if (fromY == 7 && fromX == 3
7833                && board[fromY][fromX] == BlackKing
7834                && toY == 7 && toX == 1) {
7835         board[fromY][fromX] = EmptySquare;
7836         board[toY][toX] = BlackKing;
7837         board[fromY][0] = EmptySquare;
7838         board[toY][2] = BlackRook;
7839     } else if (board[fromY][fromX] == BlackPawn
7840                && toY < promoRank
7841                && gameInfo.variant != VariantXiangqi
7842                ) {
7843         /* black pawn promotion */
7844         board[toY][toX] = CharToPiece(ToLower(promoChar));
7845         if (board[toY][toX] == EmptySquare) {
7846             board[toY][toX] = BlackQueen;
7847         }
7848         if(gameInfo.variant==VariantBughouse ||
7849            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7850             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7851         board[fromY][fromX] = EmptySquare;
7852     } else if ((fromY == 3)
7853                && (toX != fromX)
7854                && gameInfo.variant != VariantXiangqi
7855                && gameInfo.variant != VariantBerolina
7856                && (board[fromY][fromX] == BlackPawn)
7857                && (board[toY][toX] == EmptySquare)) {
7858         board[fromY][fromX] = EmptySquare;
7859         board[toY][toX] = BlackPawn;
7860         captured = board[toY + 1][toX];
7861         board[toY + 1][toX] = EmptySquare;
7862     } else if ((fromY == 3)
7863                && (toX == fromX)
7864                && gameInfo.variant == VariantBerolina
7865                && (board[fromY][fromX] == BlackPawn)
7866                && (board[toY][toX] == EmptySquare)) {
7867         board[fromY][fromX] = EmptySquare;
7868         board[toY][toX] = BlackPawn;
7869         if(oldEP & EP_BEROLIN_A) {
7870                 captured = board[fromY][fromX-1];
7871                 board[fromY][fromX-1] = EmptySquare;
7872         }else{  captured = board[fromY][fromX+1];
7873                 board[fromY][fromX+1] = EmptySquare;
7874         }
7875     } else {
7876         board[toY][toX] = board[fromY][fromX];
7877         board[fromY][fromX] = EmptySquare;
7878     }
7879
7880     /* [HGM] now we promote for Shogi, if needed */
7881     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7882         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7883   }
7884
7885     if (gameInfo.holdingsWidth != 0) {
7886
7887       /* !!A lot more code needs to be written to support holdings  */
7888       /* [HGM] OK, so I have written it. Holdings are stored in the */
7889       /* penultimate board files, so they are automaticlly stored   */
7890       /* in the game history.                                       */
7891       if (fromY == DROP_RANK) {
7892         /* Delete from holdings, by decreasing count */
7893         /* and erasing image if necessary            */
7894         p = (int) fromX;
7895         if(p < (int) BlackPawn) { /* white drop */
7896              p -= (int)WhitePawn;
7897                  p = PieceToNumber((ChessSquare)p);
7898              if(p >= gameInfo.holdingsSize) p = 0;
7899              if(--board[p][BOARD_WIDTH-2] <= 0)
7900                   board[p][BOARD_WIDTH-1] = EmptySquare;
7901              if((int)board[p][BOARD_WIDTH-2] < 0)
7902                         board[p][BOARD_WIDTH-2] = 0;
7903         } else {                  /* black drop */
7904              p -= (int)BlackPawn;
7905                  p = PieceToNumber((ChessSquare)p);
7906              if(p >= gameInfo.holdingsSize) p = 0;
7907              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7908                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7909              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7910                         board[BOARD_HEIGHT-1-p][1] = 0;
7911         }
7912       }
7913       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7914           && gameInfo.variant != VariantBughouse        ) {
7915         /* [HGM] holdings: Add to holdings, if holdings exist */
7916         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7917                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7918                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7919         }
7920         p = (int) captured;
7921         if (p >= (int) BlackPawn) {
7922           p -= (int)BlackPawn;
7923           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7924                   /* in Shogi restore piece to its original  first */
7925                   captured = (ChessSquare) (DEMOTED captured);
7926                   p = DEMOTED p;
7927           }
7928           p = PieceToNumber((ChessSquare)p);
7929           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7930           board[p][BOARD_WIDTH-2]++;
7931           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7932         } else {
7933           p -= (int)WhitePawn;
7934           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7935                   captured = (ChessSquare) (DEMOTED captured);
7936                   p = DEMOTED p;
7937           }
7938           p = PieceToNumber((ChessSquare)p);
7939           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7940           board[BOARD_HEIGHT-1-p][1]++;
7941           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7942         }
7943       }
7944     } else if (gameInfo.variant == VariantAtomic) {
7945       if (captured != EmptySquare) {
7946         int y, x;
7947         for (y = toY-1; y <= toY+1; y++) {
7948           for (x = toX-1; x <= toX+1; x++) {
7949             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7950                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7951               board[y][x] = EmptySquare;
7952             }
7953           }
7954         }
7955         board[toY][toX] = EmptySquare;
7956       }
7957     }
7958     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7959         /* [HGM] Shogi promotions */
7960         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7961     }
7962
7963     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7964                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7965         // [HGM] superchess: take promotion piece out of holdings
7966         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7967         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7968             if(!--board[k][BOARD_WIDTH-2])
7969                 board[k][BOARD_WIDTH-1] = EmptySquare;
7970         } else {
7971             if(!--board[BOARD_HEIGHT-1-k][1])
7972                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7973         }
7974     }
7975
7976 }
7977
7978 /* Updates forwardMostMove */
7979 void
7980 MakeMove(fromX, fromY, toX, toY, promoChar)
7981      int fromX, fromY, toX, toY;
7982      int promoChar;
7983 {
7984 //    forwardMostMove++; // [HGM] bare: moved downstream
7985
7986     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7987         int timeLeft; static int lastLoadFlag=0; int king, piece;
7988         piece = boards[forwardMostMove][fromY][fromX];
7989         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7990         if(gameInfo.variant == VariantKnightmate)
7991             king += (int) WhiteUnicorn - (int) WhiteKing;
7992         if(forwardMostMove == 0) {
7993             if(blackPlaysFirst) 
7994                 fprintf(serverMoves, "%s;", second.tidy);
7995             fprintf(serverMoves, "%s;", first.tidy);
7996             if(!blackPlaysFirst) 
7997                 fprintf(serverMoves, "%s;", second.tidy);
7998         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7999         lastLoadFlag = loadFlag;
8000         // print base move
8001         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8002         // print castling suffix
8003         if( toY == fromY && piece == king ) {
8004             if(toX-fromX > 1)
8005                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8006             if(fromX-toX >1)
8007                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8008         }
8009         // e.p. suffix
8010         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8011              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8012              boards[forwardMostMove][toY][toX] == EmptySquare
8013              && fromX != toX )
8014                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8015         // promotion suffix
8016         if(promoChar != NULLCHAR)
8017                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8018         if(!loadFlag) {
8019             fprintf(serverMoves, "/%d/%d",
8020                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8021             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8022             else                      timeLeft = blackTimeRemaining/1000;
8023             fprintf(serverMoves, "/%d", timeLeft);
8024         }
8025         fflush(serverMoves);
8026     }
8027
8028     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8029       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8030                         0, 1);
8031       return;
8032     }
8033     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8034     if (commentList[forwardMostMove+1] != NULL) {
8035         free(commentList[forwardMostMove+1]);
8036         commentList[forwardMostMove+1] = NULL;
8037     }
8038     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8039     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8040     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8041     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8042     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8043     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8044     gameInfo.result = GameUnfinished;
8045     if (gameInfo.resultDetails != NULL) {
8046         free(gameInfo.resultDetails);
8047         gameInfo.resultDetails = NULL;
8048     }
8049     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8050                               moveList[forwardMostMove - 1]);
8051     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8052                              PosFlags(forwardMostMove - 1),
8053                              fromY, fromX, toY, toX, promoChar,
8054                              parseList[forwardMostMove - 1]);
8055     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8056       case MT_NONE:
8057       case MT_STALEMATE:
8058       default:
8059         break;
8060       case MT_CHECK:
8061         if(gameInfo.variant != VariantShogi)
8062             strcat(parseList[forwardMostMove - 1], "+");
8063         break;
8064       case MT_CHECKMATE:
8065       case MT_STAINMATE:
8066         strcat(parseList[forwardMostMove - 1], "#");
8067         break;
8068     }
8069     if (appData.debugMode) {
8070         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8071     }
8072
8073 }
8074
8075 /* Updates currentMove if not pausing */
8076 void
8077 ShowMove(fromX, fromY, toX, toY)
8078 {
8079     int instant = (gameMode == PlayFromGameFile) ?
8080         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8081     if(appData.noGUI) return;
8082     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8083         if (!instant) {
8084             if (forwardMostMove == currentMove + 1) {
8085                 AnimateMove(boards[forwardMostMove - 1],
8086                             fromX, fromY, toX, toY);
8087             }
8088             if (appData.highlightLastMove) {
8089                 SetHighlights(fromX, fromY, toX, toY);
8090             }
8091         }
8092         currentMove = forwardMostMove;
8093     }
8094
8095     if (instant) return;
8096
8097     DisplayMove(currentMove - 1);
8098     DrawPosition(FALSE, boards[currentMove]);
8099     DisplayBothClocks();
8100     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8101 }
8102
8103 void SendEgtPath(ChessProgramState *cps)
8104 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8105         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8106
8107         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8108
8109         while(*p) {
8110             char c, *q = name+1, *r, *s;
8111
8112             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8113             while(*p && *p != ',') *q++ = *p++;
8114             *q++ = ':'; *q = 0;
8115             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8116                 strcmp(name, ",nalimov:") == 0 ) {
8117                 // take nalimov path from the menu-changeable option first, if it is defined
8118                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8119                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8120             } else
8121             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8122                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8123                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8124                 s = r = StrStr(s, ":") + 1; // beginning of path info
8125                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8126                 c = *r; *r = 0;             // temporarily null-terminate path info
8127                     *--q = 0;               // strip of trailig ':' from name
8128                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8129                 *r = c;
8130                 SendToProgram(buf,cps);     // send egtbpath command for this format
8131             }
8132             if(*p == ',') p++; // read away comma to position for next format name
8133         }
8134 }
8135
8136 void
8137 InitChessProgram(cps, setup)
8138      ChessProgramState *cps;
8139      int setup; /* [HGM] needed to setup FRC opening position */
8140 {
8141     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8142     if (appData.noChessProgram) return;
8143     hintRequested = FALSE;
8144     bookRequested = FALSE;
8145
8146     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8147     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8148     if(cps->memSize) { /* [HGM] memory */
8149         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8150         SendToProgram(buf, cps);
8151     }
8152     SendEgtPath(cps); /* [HGM] EGT */
8153     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8154         sprintf(buf, "cores %d\n", appData.smpCores);
8155         SendToProgram(buf, cps);
8156     }
8157
8158     SendToProgram(cps->initString, cps);
8159     if (gameInfo.variant != VariantNormal &&
8160         gameInfo.variant != VariantLoadable
8161         /* [HGM] also send variant if board size non-standard */
8162         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8163                                             ) {
8164       char *v = VariantName(gameInfo.variant);
8165       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8166         /* [HGM] in protocol 1 we have to assume all variants valid */
8167         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8168         DisplayFatalError(buf, 0, 1);
8169         return;
8170       }
8171
8172       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8173       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8174       if( gameInfo.variant == VariantXiangqi )
8175            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8176       if( gameInfo.variant == VariantShogi )
8177            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8178       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8179            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8180       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8181                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8182            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8183       if( gameInfo.variant == VariantCourier )
8184            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8185       if( gameInfo.variant == VariantSuper )
8186            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8187       if( gameInfo.variant == VariantGreat )
8188            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8189
8190       if(overruled) {
8191            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8192                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8193            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8194            if(StrStr(cps->variants, b) == NULL) { 
8195                // specific sized variant not known, check if general sizing allowed
8196                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8197                    if(StrStr(cps->variants, "boardsize") == NULL) {
8198                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8199                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8200                        DisplayFatalError(buf, 0, 1);
8201                        return;
8202                    }
8203                    /* [HGM] here we really should compare with the maximum supported board size */
8204                }
8205            }
8206       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8207       sprintf(buf, "variant %s\n", b);
8208       SendToProgram(buf, cps);
8209     }
8210     currentlyInitializedVariant = gameInfo.variant;
8211
8212     /* [HGM] send opening position in FRC to first engine */
8213     if(setup) {
8214           SendToProgram("force\n", cps);
8215           SendBoard(cps, 0);
8216           /* engine is now in force mode! Set flag to wake it up after first move. */
8217           setboardSpoiledMachineBlack = 1;
8218     }
8219
8220     if (cps->sendICS) {
8221       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8222       SendToProgram(buf, cps);
8223     }
8224     cps->maybeThinking = FALSE;
8225     cps->offeredDraw = 0;
8226     if (!appData.icsActive) {
8227         SendTimeControl(cps, movesPerSession, timeControl,
8228                         timeIncrement, appData.searchDepth,
8229                         searchTime);
8230     }
8231     if (appData.showThinking 
8232         // [HGM] thinking: four options require thinking output to be sent
8233         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8234                                 ) {
8235         SendToProgram("post\n", cps);
8236     }
8237     SendToProgram("hard\n", cps);
8238     if (!appData.ponderNextMove) {
8239         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8240            it without being sure what state we are in first.  "hard"
8241            is not a toggle, so that one is OK.
8242          */
8243         SendToProgram("easy\n", cps);
8244     }
8245     if (cps->usePing) {
8246       sprintf(buf, "ping %d\n", ++cps->lastPing);
8247       SendToProgram(buf, cps);
8248     }
8249     cps->initDone = TRUE;
8250 }   
8251
8252
8253 void
8254 StartChessProgram(cps)
8255      ChessProgramState *cps;
8256 {
8257     char buf[MSG_SIZ];
8258     int err;
8259
8260     if (appData.noChessProgram) return;
8261     cps->initDone = FALSE;
8262
8263     if (strcmp(cps->host, "localhost") == 0) {
8264         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8265     } else if (*appData.remoteShell == NULLCHAR) {
8266         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8267     } else {
8268         if (*appData.remoteUser == NULLCHAR) {
8269           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8270                     cps->program);
8271         } else {
8272           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8273                     cps->host, appData.remoteUser, cps->program);
8274         }
8275         err = StartChildProcess(buf, "", &cps->pr);
8276     }
8277     
8278     if (err != 0) {
8279         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8280         DisplayFatalError(buf, err, 1);
8281         cps->pr = NoProc;
8282         cps->isr = NULL;
8283         return;
8284     }
8285     
8286     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8287     if (cps->protocolVersion > 1) {
8288       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8289       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8290       cps->comboCnt = 0;  //                and values of combo boxes
8291       SendToProgram(buf, cps);
8292     } else {
8293       SendToProgram("xboard\n", cps);
8294     }
8295 }
8296
8297
8298 void
8299 TwoMachinesEventIfReady P((void))
8300 {
8301   if (first.lastPing != first.lastPong) {
8302     DisplayMessage("", _("Waiting for first chess program"));
8303     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8304     return;
8305   }
8306   if (second.lastPing != second.lastPong) {
8307     DisplayMessage("", _("Waiting for second chess program"));
8308     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8309     return;
8310   }
8311   ThawUI();
8312   TwoMachinesEvent();
8313 }
8314
8315 void
8316 NextMatchGame P((void))
8317 {
8318     int index; /* [HGM] autoinc: step load index during match */
8319     Reset(FALSE, TRUE);
8320     if (*appData.loadGameFile != NULLCHAR) {
8321         index = appData.loadGameIndex;
8322         if(index < 0) { // [HGM] autoinc
8323             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8324             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8325         } 
8326         LoadGameFromFile(appData.loadGameFile,
8327                          index,
8328                          appData.loadGameFile, FALSE);
8329     } else if (*appData.loadPositionFile != NULLCHAR) {
8330         index = appData.loadPositionIndex;
8331         if(index < 0) { // [HGM] autoinc
8332             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8333             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8334         } 
8335         LoadPositionFromFile(appData.loadPositionFile,
8336                              index,
8337                              appData.loadPositionFile);
8338     }
8339     TwoMachinesEventIfReady();
8340 }
8341
8342 void UserAdjudicationEvent( int result )
8343 {
8344     ChessMove gameResult = GameIsDrawn;
8345
8346     if( result > 0 ) {
8347         gameResult = WhiteWins;
8348     }
8349     else if( result < 0 ) {
8350         gameResult = BlackWins;
8351     }
8352
8353     if( gameMode == TwoMachinesPlay ) {
8354         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8355     }
8356 }
8357
8358
8359 // [HGM] save: calculate checksum of game to make games easily identifiable
8360 int StringCheckSum(char *s)
8361 {
8362         int i = 0;
8363         if(s==NULL) return 0;
8364         while(*s) i = i*259 + *s++;
8365         return i;
8366 }
8367
8368 int GameCheckSum()
8369 {
8370         int i, sum=0;
8371         for(i=backwardMostMove; i<forwardMostMove; i++) {
8372                 sum += pvInfoList[i].depth;
8373                 sum += StringCheckSum(parseList[i]);
8374                 sum += StringCheckSum(commentList[i]);
8375                 sum *= 261;
8376         }
8377         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8378         return sum + StringCheckSum(commentList[i]);
8379 } // end of save patch
8380
8381 void
8382 GameEnds(result, resultDetails, whosays)
8383      ChessMove result;
8384      char *resultDetails;
8385      int whosays;
8386 {
8387     GameMode nextGameMode;
8388     int isIcsGame;
8389     char buf[MSG_SIZ];
8390
8391     if(endingGame) return; /* [HGM] crash: forbid recursion */
8392     endingGame = 1;
8393
8394     if (appData.debugMode) {
8395       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8396               result, resultDetails ? resultDetails : "(null)", whosays);
8397     }
8398
8399     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8400         /* If we are playing on ICS, the server decides when the
8401            game is over, but the engine can offer to draw, claim 
8402            a draw, or resign. 
8403          */
8404 #if ZIPPY
8405         if (appData.zippyPlay && first.initDone) {
8406             if (result == GameIsDrawn) {
8407                 /* In case draw still needs to be claimed */
8408                 SendToICS(ics_prefix);
8409                 SendToICS("draw\n");
8410             } else if (StrCaseStr(resultDetails, "resign")) {
8411                 SendToICS(ics_prefix);
8412                 SendToICS("resign\n");
8413             }
8414         }
8415 #endif
8416         endingGame = 0; /* [HGM] crash */
8417         return;
8418     }
8419
8420     /* If we're loading the game from a file, stop */
8421     if (whosays == GE_FILE) {
8422       (void) StopLoadGameTimer();
8423       gameFileFP = NULL;
8424     }
8425
8426     /* Cancel draw offers */
8427     first.offeredDraw = second.offeredDraw = 0;
8428
8429     /* If this is an ICS game, only ICS can really say it's done;
8430        if not, anyone can. */
8431     isIcsGame = (gameMode == IcsPlayingWhite || 
8432                  gameMode == IcsPlayingBlack || 
8433                  gameMode == IcsObserving    || 
8434                  gameMode == IcsExamining);
8435
8436     if (!isIcsGame || whosays == GE_ICS) {
8437         /* OK -- not an ICS game, or ICS said it was done */
8438         StopClocks();
8439         if (!isIcsGame && !appData.noChessProgram) 
8440           SetUserThinkingEnables();
8441     
8442         /* [HGM] if a machine claims the game end we verify this claim */
8443         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8444             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8445                 char claimer;
8446                 ChessMove trueResult = (ChessMove) -1;
8447
8448                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8449                                             first.twoMachinesColor[0] :
8450                                             second.twoMachinesColor[0] ;
8451
8452                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8453                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8454                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8455                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8456                 } else
8457                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8458                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8459                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8460                 } else
8461                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8462                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8463                 }
8464
8465                 // now verify win claims, but not in drop games, as we don't understand those yet
8466                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8467                                                  || gameInfo.variant == VariantGreat) &&
8468                     (result == WhiteWins && claimer == 'w' ||
8469                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8470                       if (appData.debugMode) {
8471                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8472                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8473                       }
8474                       if(result != trueResult) {
8475                               sprintf(buf, "False win claim: '%s'", resultDetails);
8476                               result = claimer == 'w' ? BlackWins : WhiteWins;
8477                               resultDetails = buf;
8478                       }
8479                 } else
8480                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8481                     && (forwardMostMove <= backwardMostMove ||
8482                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8483                         (claimer=='b')==(forwardMostMove&1))
8484                                                                                   ) {
8485                       /* [HGM] verify: draws that were not flagged are false claims */
8486                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8487                       result = claimer == 'w' ? BlackWins : WhiteWins;
8488                       resultDetails = buf;
8489                 }
8490                 /* (Claiming a loss is accepted no questions asked!) */
8491             }
8492             /* [HGM] bare: don't allow bare King to win */
8493             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8494                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8495                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8496                && result != GameIsDrawn)
8497             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8498                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8499                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8500                         if(p >= 0 && p <= (int)WhiteKing) k++;
8501                 }
8502                 if (appData.debugMode) {
8503                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8504                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8505                 }
8506                 if(k <= 1) {
8507                         result = GameIsDrawn;
8508                         sprintf(buf, "%s but bare king", resultDetails);
8509                         resultDetails = buf;
8510                 }
8511             }
8512         }
8513
8514
8515         if(serverMoves != NULL && !loadFlag) { char c = '=';
8516             if(result==WhiteWins) c = '+';
8517             if(result==BlackWins) c = '-';
8518             if(resultDetails != NULL)
8519                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8520         }
8521         if (resultDetails != NULL) {
8522             gameInfo.result = result;
8523             gameInfo.resultDetails = StrSave(resultDetails);
8524
8525             /* display last move only if game was not loaded from file */
8526             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8527                 DisplayMove(currentMove - 1);
8528     
8529             if (forwardMostMove != 0) {
8530                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8531                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8532                                                                 ) {
8533                     if (*appData.saveGameFile != NULLCHAR) {
8534                         SaveGameToFile(appData.saveGameFile, TRUE);
8535                     } else if (appData.autoSaveGames) {
8536                         AutoSaveGame();
8537                     }
8538                     if (*appData.savePositionFile != NULLCHAR) {
8539                         SavePositionToFile(appData.savePositionFile);
8540                     }
8541                 }
8542             }
8543
8544             /* Tell program how game ended in case it is learning */
8545             /* [HGM] Moved this to after saving the PGN, just in case */
8546             /* engine died and we got here through time loss. In that */
8547             /* case we will get a fatal error writing the pipe, which */
8548             /* would otherwise lose us the PGN.                       */
8549             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8550             /* output during GameEnds should never be fatal anymore   */
8551             if (gameMode == MachinePlaysWhite ||
8552                 gameMode == MachinePlaysBlack ||
8553                 gameMode == TwoMachinesPlay ||
8554                 gameMode == IcsPlayingWhite ||
8555                 gameMode == IcsPlayingBlack ||
8556                 gameMode == BeginningOfGame) {
8557                 char buf[MSG_SIZ];
8558                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8559                         resultDetails);
8560                 if (first.pr != NoProc) {
8561                     SendToProgram(buf, &first);
8562                 }
8563                 if (second.pr != NoProc &&
8564                     gameMode == TwoMachinesPlay) {
8565                     SendToProgram(buf, &second);
8566                 }
8567             }
8568         }
8569
8570         if (appData.icsActive) {
8571             if (appData.quietPlay &&
8572                 (gameMode == IcsPlayingWhite ||
8573                  gameMode == IcsPlayingBlack)) {
8574                 SendToICS(ics_prefix);
8575                 SendToICS("set shout 1\n");
8576             }
8577             nextGameMode = IcsIdle;
8578             ics_user_moved = FALSE;
8579             /* clean up premove.  It's ugly when the game has ended and the
8580              * premove highlights are still on the board.
8581              */
8582             if (gotPremove) {
8583               gotPremove = FALSE;
8584               ClearPremoveHighlights();
8585               DrawPosition(FALSE, boards[currentMove]);
8586             }
8587             if (whosays == GE_ICS) {
8588                 switch (result) {
8589                 case WhiteWins:
8590                     if (gameMode == IcsPlayingWhite)
8591                         PlayIcsWinSound();
8592                     else if(gameMode == IcsPlayingBlack)
8593                         PlayIcsLossSound();
8594                     break;
8595                 case BlackWins:
8596                     if (gameMode == IcsPlayingBlack)
8597                         PlayIcsWinSound();
8598                     else if(gameMode == IcsPlayingWhite)
8599                         PlayIcsLossSound();
8600                     break;
8601                 case GameIsDrawn:
8602                     PlayIcsDrawSound();
8603                     break;
8604                 default:
8605                     PlayIcsUnfinishedSound();
8606                 }
8607             }
8608         } else if (gameMode == EditGame ||
8609                    gameMode == PlayFromGameFile || 
8610                    gameMode == AnalyzeMode || 
8611                    gameMode == AnalyzeFile) {
8612             nextGameMode = gameMode;
8613         } else {
8614             nextGameMode = EndOfGame;
8615         }
8616         pausing = FALSE;
8617         ModeHighlight();
8618     } else {
8619         nextGameMode = gameMode;
8620     }
8621
8622     if (appData.noChessProgram) {
8623         gameMode = nextGameMode;
8624         ModeHighlight();
8625         endingGame = 0; /* [HGM] crash */
8626         return;
8627     }
8628
8629     if (first.reuse) {
8630         /* Put first chess program into idle state */
8631         if (first.pr != NoProc &&
8632             (gameMode == MachinePlaysWhite ||
8633              gameMode == MachinePlaysBlack ||
8634              gameMode == TwoMachinesPlay ||
8635              gameMode == IcsPlayingWhite ||
8636              gameMode == IcsPlayingBlack ||
8637              gameMode == BeginningOfGame)) {
8638             SendToProgram("force\n", &first);
8639             if (first.usePing) {
8640               char buf[MSG_SIZ];
8641               sprintf(buf, "ping %d\n", ++first.lastPing);
8642               SendToProgram(buf, &first);
8643             }
8644         }
8645     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8646         /* Kill off first chess program */
8647         if (first.isr != NULL)
8648           RemoveInputSource(first.isr);
8649         first.isr = NULL;
8650     
8651         if (first.pr != NoProc) {
8652             ExitAnalyzeMode();
8653             DoSleep( appData.delayBeforeQuit );
8654             SendToProgram("quit\n", &first);
8655             DoSleep( appData.delayAfterQuit );
8656             DestroyChildProcess(first.pr, first.useSigterm);
8657         }
8658         first.pr = NoProc;
8659     }
8660     if (second.reuse) {
8661         /* Put second chess program into idle state */
8662         if (second.pr != NoProc &&
8663             gameMode == TwoMachinesPlay) {
8664             SendToProgram("force\n", &second);
8665             if (second.usePing) {
8666               char buf[MSG_SIZ];
8667               sprintf(buf, "ping %d\n", ++second.lastPing);
8668               SendToProgram(buf, &second);
8669             }
8670         }
8671     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8672         /* Kill off second chess program */
8673         if (second.isr != NULL)
8674           RemoveInputSource(second.isr);
8675         second.isr = NULL;
8676     
8677         if (second.pr != NoProc) {
8678             DoSleep( appData.delayBeforeQuit );
8679             SendToProgram("quit\n", &second);
8680             DoSleep( appData.delayAfterQuit );
8681             DestroyChildProcess(second.pr, second.useSigterm);
8682         }
8683         second.pr = NoProc;
8684     }
8685
8686     if (matchMode && gameMode == TwoMachinesPlay) {
8687         switch (result) {
8688         case WhiteWins:
8689           if (first.twoMachinesColor[0] == 'w') {
8690             first.matchWins++;
8691           } else {
8692             second.matchWins++;
8693           }
8694           break;
8695         case BlackWins:
8696           if (first.twoMachinesColor[0] == 'b') {
8697             first.matchWins++;
8698           } else {
8699             second.matchWins++;
8700           }
8701           break;
8702         default:
8703           break;
8704         }
8705         if (matchGame < appData.matchGames) {
8706             char *tmp;
8707             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8708                 tmp = first.twoMachinesColor;
8709                 first.twoMachinesColor = second.twoMachinesColor;
8710                 second.twoMachinesColor = tmp;
8711             }
8712             gameMode = nextGameMode;
8713             matchGame++;
8714             if(appData.matchPause>10000 || appData.matchPause<10)
8715                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8716             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8717             endingGame = 0; /* [HGM] crash */
8718             return;
8719         } else {
8720             char buf[MSG_SIZ];
8721             gameMode = nextGameMode;
8722             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8723                     first.tidy, second.tidy,
8724                     first.matchWins, second.matchWins,
8725                     appData.matchGames - (first.matchWins + second.matchWins));
8726             DisplayFatalError(buf, 0, 0);
8727         }
8728     }
8729     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8730         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8731       ExitAnalyzeMode();
8732     gameMode = nextGameMode;
8733     ModeHighlight();
8734     endingGame = 0;  /* [HGM] crash */
8735 }
8736
8737 /* Assumes program was just initialized (initString sent).
8738    Leaves program in force mode. */
8739 void
8740 FeedMovesToProgram(cps, upto) 
8741      ChessProgramState *cps;
8742      int upto;
8743 {
8744     int i;
8745     
8746     if (appData.debugMode)
8747       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8748               startedFromSetupPosition ? "position and " : "",
8749               backwardMostMove, upto, cps->which);
8750     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8751         // [HGM] variantswitch: make engine aware of new variant
8752         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8753                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8754         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8755         SendToProgram(buf, cps);
8756         currentlyInitializedVariant = gameInfo.variant;
8757     }
8758     SendToProgram("force\n", cps);
8759     if (startedFromSetupPosition) {
8760         SendBoard(cps, backwardMostMove);
8761     if (appData.debugMode) {
8762         fprintf(debugFP, "feedMoves\n");
8763     }
8764     }
8765     for (i = backwardMostMove; i < upto; i++) {
8766         SendMoveToProgram(i, cps);
8767     }
8768 }
8769
8770
8771 void
8772 ResurrectChessProgram()
8773 {
8774      /* The chess program may have exited.
8775         If so, restart it and feed it all the moves made so far. */
8776
8777     if (appData.noChessProgram || first.pr != NoProc) return;
8778     
8779     StartChessProgram(&first);
8780     InitChessProgram(&first, FALSE);
8781     FeedMovesToProgram(&first, currentMove);
8782
8783     if (!first.sendTime) {
8784         /* can't tell gnuchess what its clock should read,
8785            so we bow to its notion. */
8786         ResetClocks();
8787         timeRemaining[0][currentMove] = whiteTimeRemaining;
8788         timeRemaining[1][currentMove] = blackTimeRemaining;
8789     }
8790
8791     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8792                 appData.icsEngineAnalyze) && first.analysisSupport) {
8793       SendToProgram("analyze\n", &first);
8794       first.analyzing = TRUE;
8795     }
8796 }
8797
8798 /*
8799  * Button procedures
8800  */
8801 void
8802 Reset(redraw, init)
8803      int redraw, init;
8804 {
8805     int i;
8806
8807     if (appData.debugMode) {
8808         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8809                 redraw, init, gameMode);
8810     }
8811     CleanupTail(); // [HGM] vari: delete any stored variations
8812     pausing = pauseExamInvalid = FALSE;
8813     startedFromSetupPosition = blackPlaysFirst = FALSE;
8814     firstMove = TRUE;
8815     whiteFlag = blackFlag = FALSE;
8816     userOfferedDraw = FALSE;
8817     hintRequested = bookRequested = FALSE;
8818     first.maybeThinking = FALSE;
8819     second.maybeThinking = FALSE;
8820     first.bookSuspend = FALSE; // [HGM] book
8821     second.bookSuspend = FALSE;
8822     thinkOutput[0] = NULLCHAR;
8823     lastHint[0] = NULLCHAR;
8824     ClearGameInfo(&gameInfo);
8825     gameInfo.variant = StringToVariant(appData.variant);
8826     ics_user_moved = ics_clock_paused = FALSE;
8827     ics_getting_history = H_FALSE;
8828     ics_gamenum = -1;
8829     white_holding[0] = black_holding[0] = NULLCHAR;
8830     ClearProgramStats();
8831     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8832     
8833     ResetFrontEnd();
8834     ClearHighlights();
8835     flipView = appData.flipView;
8836     ClearPremoveHighlights();
8837     gotPremove = FALSE;
8838     alarmSounded = FALSE;
8839
8840     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8841     if(appData.serverMovesName != NULL) {
8842         /* [HGM] prepare to make moves file for broadcasting */
8843         clock_t t = clock();
8844         if(serverMoves != NULL) fclose(serverMoves);
8845         serverMoves = fopen(appData.serverMovesName, "r");
8846         if(serverMoves != NULL) {
8847             fclose(serverMoves);
8848             /* delay 15 sec before overwriting, so all clients can see end */
8849             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8850         }
8851         serverMoves = fopen(appData.serverMovesName, "w");
8852     }
8853
8854     ExitAnalyzeMode();
8855     gameMode = BeginningOfGame;
8856     ModeHighlight();
8857     if(appData.icsActive) gameInfo.variant = VariantNormal;
8858     currentMove = forwardMostMove = backwardMostMove = 0;
8859     InitPosition(redraw);
8860     for (i = 0; i < MAX_MOVES; i++) {
8861         if (commentList[i] != NULL) {
8862             free(commentList[i]);
8863             commentList[i] = NULL;
8864         }
8865     }
8866     ResetClocks();
8867     timeRemaining[0][0] = whiteTimeRemaining;
8868     timeRemaining[1][0] = blackTimeRemaining;
8869     if (first.pr == NULL) {
8870         StartChessProgram(&first);
8871     }
8872     if (init) {
8873             InitChessProgram(&first, startedFromSetupPosition);
8874     }
8875     DisplayTitle("");
8876     DisplayMessage("", "");
8877     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8878     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8879 }
8880
8881 void
8882 AutoPlayGameLoop()
8883 {
8884     for (;;) {
8885         if (!AutoPlayOneMove())
8886           return;
8887         if (matchMode || appData.timeDelay == 0)
8888           continue;
8889         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8890           return;
8891         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8892         break;
8893     }
8894 }
8895
8896
8897 int
8898 AutoPlayOneMove()
8899 {
8900     int fromX, fromY, toX, toY;
8901
8902     if (appData.debugMode) {
8903       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8904     }
8905
8906     if (gameMode != PlayFromGameFile)
8907       return FALSE;
8908
8909     if (currentMove >= forwardMostMove) {
8910       gameMode = EditGame;
8911       ModeHighlight();
8912
8913       /* [AS] Clear current move marker at the end of a game */
8914       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8915
8916       return FALSE;
8917     }
8918     
8919     toX = moveList[currentMove][2] - AAA;
8920     toY = moveList[currentMove][3] - ONE;
8921
8922     if (moveList[currentMove][1] == '@') {
8923         if (appData.highlightLastMove) {
8924             SetHighlights(-1, -1, toX, toY);
8925         }
8926     } else {
8927         fromX = moveList[currentMove][0] - AAA;
8928         fromY = moveList[currentMove][1] - ONE;
8929
8930         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8931
8932         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8933
8934         if (appData.highlightLastMove) {
8935             SetHighlights(fromX, fromY, toX, toY);
8936         }
8937     }
8938     DisplayMove(currentMove);
8939     SendMoveToProgram(currentMove++, &first);
8940     DisplayBothClocks();
8941     DrawPosition(FALSE, boards[currentMove]);
8942     // [HGM] PV info: always display, routine tests if empty
8943     DisplayComment(currentMove - 1, commentList[currentMove]);
8944     return TRUE;
8945 }
8946
8947
8948 int
8949 LoadGameOneMove(readAhead)
8950      ChessMove readAhead;
8951 {
8952     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8953     char promoChar = NULLCHAR;
8954     ChessMove moveType;
8955     char move[MSG_SIZ];
8956     char *p, *q;
8957     
8958     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8959         gameMode != AnalyzeMode && gameMode != Training) {
8960         gameFileFP = NULL;
8961         return FALSE;
8962     }
8963     
8964     yyboardindex = forwardMostMove;
8965     if (readAhead != (ChessMove)0) {
8966       moveType = readAhead;
8967     } else {
8968       if (gameFileFP == NULL)
8969           return FALSE;
8970       moveType = (ChessMove) yylex();
8971     }
8972     
8973     done = FALSE;
8974     switch (moveType) {
8975       case Comment:
8976         if (appData.debugMode) 
8977           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8978         p = yy_text;
8979
8980         /* append the comment but don't display it */
8981         AppendComment(currentMove, p, FALSE);
8982         return TRUE;
8983
8984       case WhiteCapturesEnPassant:
8985       case BlackCapturesEnPassant:
8986       case WhitePromotionChancellor:
8987       case BlackPromotionChancellor:
8988       case WhitePromotionArchbishop:
8989       case BlackPromotionArchbishop:
8990       case WhitePromotionCentaur:
8991       case BlackPromotionCentaur:
8992       case WhitePromotionQueen:
8993       case BlackPromotionQueen:
8994       case WhitePromotionRook:
8995       case BlackPromotionRook:
8996       case WhitePromotionBishop:
8997       case BlackPromotionBishop:
8998       case WhitePromotionKnight:
8999       case BlackPromotionKnight:
9000       case WhitePromotionKing:
9001       case BlackPromotionKing:
9002       case NormalMove:
9003       case WhiteKingSideCastle:
9004       case WhiteQueenSideCastle:
9005       case BlackKingSideCastle:
9006       case BlackQueenSideCastle:
9007       case WhiteKingSideCastleWild:
9008       case WhiteQueenSideCastleWild:
9009       case BlackKingSideCastleWild:
9010       case BlackQueenSideCastleWild:
9011       /* PUSH Fabien */
9012       case WhiteHSideCastleFR:
9013       case WhiteASideCastleFR:
9014       case BlackHSideCastleFR:
9015       case BlackASideCastleFR:
9016       /* POP Fabien */
9017         if (appData.debugMode)
9018           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9019         fromX = currentMoveString[0] - AAA;
9020         fromY = currentMoveString[1] - ONE;
9021         toX = currentMoveString[2] - AAA;
9022         toY = currentMoveString[3] - ONE;
9023         promoChar = currentMoveString[4];
9024         break;
9025
9026       case WhiteDrop:
9027       case BlackDrop:
9028         if (appData.debugMode)
9029           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9030         fromX = moveType == WhiteDrop ?
9031           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9032         (int) CharToPiece(ToLower(currentMoveString[0]));
9033         fromY = DROP_RANK;
9034         toX = currentMoveString[2] - AAA;
9035         toY = currentMoveString[3] - ONE;
9036         break;
9037
9038       case WhiteWins:
9039       case BlackWins:
9040       case GameIsDrawn:
9041       case GameUnfinished:
9042         if (appData.debugMode)
9043           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9044         p = strchr(yy_text, '{');
9045         if (p == NULL) p = strchr(yy_text, '(');
9046         if (p == NULL) {
9047             p = yy_text;
9048             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9049         } else {
9050             q = strchr(p, *p == '{' ? '}' : ')');
9051             if (q != NULL) *q = NULLCHAR;
9052             p++;
9053         }
9054         GameEnds(moveType, p, GE_FILE);
9055         done = TRUE;
9056         if (cmailMsgLoaded) {
9057             ClearHighlights();
9058             flipView = WhiteOnMove(currentMove);
9059             if (moveType == GameUnfinished) flipView = !flipView;
9060             if (appData.debugMode)
9061               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9062         }
9063         break;
9064
9065       case (ChessMove) 0:       /* end of file */
9066         if (appData.debugMode)
9067           fprintf(debugFP, "Parser hit end of file\n");
9068         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9069           case MT_NONE:
9070           case MT_CHECK:
9071             break;
9072           case MT_CHECKMATE:
9073           case MT_STAINMATE:
9074             if (WhiteOnMove(currentMove)) {
9075                 GameEnds(BlackWins, "Black mates", GE_FILE);
9076             } else {
9077                 GameEnds(WhiteWins, "White mates", GE_FILE);
9078             }
9079             break;
9080           case MT_STALEMATE:
9081             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9082             break;
9083         }
9084         done = TRUE;
9085         break;
9086
9087       case MoveNumberOne:
9088         if (lastLoadGameStart == GNUChessGame) {
9089             /* GNUChessGames have numbers, but they aren't move numbers */
9090             if (appData.debugMode)
9091               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9092                       yy_text, (int) moveType);
9093             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9094         }
9095         /* else fall thru */
9096
9097       case XBoardGame:
9098       case GNUChessGame:
9099       case PGNTag:
9100         /* Reached start of next game in file */
9101         if (appData.debugMode)
9102           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9103         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9104           case MT_NONE:
9105           case MT_CHECK:
9106             break;
9107           case MT_CHECKMATE:
9108           case MT_STAINMATE:
9109             if (WhiteOnMove(currentMove)) {
9110                 GameEnds(BlackWins, "Black mates", GE_FILE);
9111             } else {
9112                 GameEnds(WhiteWins, "White mates", GE_FILE);
9113             }
9114             break;
9115           case MT_STALEMATE:
9116             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9117             break;
9118         }
9119         done = TRUE;
9120         break;
9121
9122       case PositionDiagram:     /* should not happen; ignore */
9123       case ElapsedTime:         /* ignore */
9124       case NAG:                 /* ignore */
9125         if (appData.debugMode)
9126           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9127                   yy_text, (int) moveType);
9128         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9129
9130       case IllegalMove:
9131         if (appData.testLegality) {
9132             if (appData.debugMode)
9133               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9134             sprintf(move, _("Illegal move: %d.%s%s"),
9135                     (forwardMostMove / 2) + 1,
9136                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9137             DisplayError(move, 0);
9138             done = TRUE;
9139         } else {
9140             if (appData.debugMode)
9141               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9142                       yy_text, currentMoveString);
9143             fromX = currentMoveString[0] - AAA;
9144             fromY = currentMoveString[1] - ONE;
9145             toX = currentMoveString[2] - AAA;
9146             toY = currentMoveString[3] - ONE;
9147             promoChar = currentMoveString[4];
9148         }
9149         break;
9150
9151       case AmbiguousMove:
9152         if (appData.debugMode)
9153           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9154         sprintf(move, _("Ambiguous move: %d.%s%s"),
9155                 (forwardMostMove / 2) + 1,
9156                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9157         DisplayError(move, 0);
9158         done = TRUE;
9159         break;
9160
9161       default:
9162       case ImpossibleMove:
9163         if (appData.debugMode)
9164           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9165         sprintf(move, _("Illegal move: %d.%s%s"),
9166                 (forwardMostMove / 2) + 1,
9167                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9168         DisplayError(move, 0);
9169         done = TRUE;
9170         break;
9171     }
9172
9173     if (done) {
9174         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9175             DrawPosition(FALSE, boards[currentMove]);
9176             DisplayBothClocks();
9177             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9178               DisplayComment(currentMove - 1, commentList[currentMove]);
9179         }
9180         (void) StopLoadGameTimer();
9181         gameFileFP = NULL;
9182         cmailOldMove = forwardMostMove;
9183         return FALSE;
9184     } else {
9185         /* currentMoveString is set as a side-effect of yylex */
9186         strcat(currentMoveString, "\n");
9187         strcpy(moveList[forwardMostMove], currentMoveString);
9188         
9189         thinkOutput[0] = NULLCHAR;
9190         MakeMove(fromX, fromY, toX, toY, promoChar);
9191         currentMove = forwardMostMove;
9192         return TRUE;
9193     }
9194 }
9195
9196 /* Load the nth game from the given file */
9197 int
9198 LoadGameFromFile(filename, n, title, useList)
9199      char *filename;
9200      int n;
9201      char *title;
9202      /*Boolean*/ int useList;
9203 {
9204     FILE *f;
9205     char buf[MSG_SIZ];
9206
9207     if (strcmp(filename, "-") == 0) {
9208         f = stdin;
9209         title = "stdin";
9210     } else {
9211         f = fopen(filename, "rb");
9212         if (f == NULL) {
9213           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9214             DisplayError(buf, errno);
9215             return FALSE;
9216         }
9217     }
9218     if (fseek(f, 0, 0) == -1) {
9219         /* f is not seekable; probably a pipe */
9220         useList = FALSE;
9221     }
9222     if (useList && n == 0) {
9223         int error = GameListBuild(f);
9224         if (error) {
9225             DisplayError(_("Cannot build game list"), error);
9226         } else if (!ListEmpty(&gameList) &&
9227                    ((ListGame *) gameList.tailPred)->number > 1) {
9228             GameListPopUp(f, title);
9229             return TRUE;
9230         }
9231         GameListDestroy();
9232         n = 1;
9233     }
9234     if (n == 0) n = 1;
9235     return LoadGame(f, n, title, FALSE);
9236 }
9237
9238
9239 void
9240 MakeRegisteredMove()
9241 {
9242     int fromX, fromY, toX, toY;
9243     char promoChar;
9244     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9245         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9246           case CMAIL_MOVE:
9247           case CMAIL_DRAW:
9248             if (appData.debugMode)
9249               fprintf(debugFP, "Restoring %s for game %d\n",
9250                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9251     
9252             thinkOutput[0] = NULLCHAR;
9253             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9254             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9255             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9256             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9257             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9258             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9259             MakeMove(fromX, fromY, toX, toY, promoChar);
9260             ShowMove(fromX, fromY, toX, toY);
9261               
9262             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9263               case MT_NONE:
9264               case MT_CHECK:
9265                 break;
9266                 
9267               case MT_CHECKMATE:
9268               case MT_STAINMATE:
9269                 if (WhiteOnMove(currentMove)) {
9270                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9271                 } else {
9272                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9273                 }
9274                 break;
9275                 
9276               case MT_STALEMATE:
9277                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9278                 break;
9279             }
9280
9281             break;
9282             
9283           case CMAIL_RESIGN:
9284             if (WhiteOnMove(currentMove)) {
9285                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9286             } else {
9287                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9288             }
9289             break;
9290             
9291           case CMAIL_ACCEPT:
9292             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9293             break;
9294               
9295           default:
9296             break;
9297         }
9298     }
9299
9300     return;
9301 }
9302
9303 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9304 int
9305 CmailLoadGame(f, gameNumber, title, useList)
9306      FILE *f;
9307      int gameNumber;
9308      char *title;
9309      int useList;
9310 {
9311     int retVal;
9312
9313     if (gameNumber > nCmailGames) {
9314         DisplayError(_("No more games in this message"), 0);
9315         return FALSE;
9316     }
9317     if (f == lastLoadGameFP) {
9318         int offset = gameNumber - lastLoadGameNumber;
9319         if (offset == 0) {
9320             cmailMsg[0] = NULLCHAR;
9321             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9322                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9323                 nCmailMovesRegistered--;
9324             }
9325             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9326             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9327                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9328             }
9329         } else {
9330             if (! RegisterMove()) return FALSE;
9331         }
9332     }
9333
9334     retVal = LoadGame(f, gameNumber, title, useList);
9335
9336     /* Make move registered during previous look at this game, if any */
9337     MakeRegisteredMove();
9338
9339     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9340         commentList[currentMove]
9341           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9342         DisplayComment(currentMove - 1, commentList[currentMove]);
9343     }
9344
9345     return retVal;
9346 }
9347
9348 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9349 int
9350 ReloadGame(offset)
9351      int offset;
9352 {
9353     int gameNumber = lastLoadGameNumber + offset;
9354     if (lastLoadGameFP == NULL) {
9355         DisplayError(_("No game has been loaded yet"), 0);
9356         return FALSE;
9357     }
9358     if (gameNumber <= 0) {
9359         DisplayError(_("Can't back up any further"), 0);
9360         return FALSE;
9361     }
9362     if (cmailMsgLoaded) {
9363         return CmailLoadGame(lastLoadGameFP, gameNumber,
9364                              lastLoadGameTitle, lastLoadGameUseList);
9365     } else {
9366         return LoadGame(lastLoadGameFP, gameNumber,
9367                         lastLoadGameTitle, lastLoadGameUseList);
9368     }
9369 }
9370
9371
9372
9373 /* Load the nth game from open file f */
9374 int
9375 LoadGame(f, gameNumber, title, useList)
9376      FILE *f;
9377      int gameNumber;
9378      char *title;
9379      int useList;
9380 {
9381     ChessMove cm;
9382     char buf[MSG_SIZ];
9383     int gn = gameNumber;
9384     ListGame *lg = NULL;
9385     int numPGNTags = 0;
9386     int err;
9387     GameMode oldGameMode;
9388     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9389
9390     if (appData.debugMode) 
9391         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9392
9393     if (gameMode == Training )
9394         SetTrainingModeOff();
9395
9396     oldGameMode = gameMode;
9397     if (gameMode != BeginningOfGame) {
9398       Reset(FALSE, TRUE);
9399     }
9400
9401     gameFileFP = f;
9402     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9403         fclose(lastLoadGameFP);
9404     }
9405
9406     if (useList) {
9407         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9408         
9409         if (lg) {
9410             fseek(f, lg->offset, 0);
9411             GameListHighlight(gameNumber);
9412             gn = 1;
9413         }
9414         else {
9415             DisplayError(_("Game number out of range"), 0);
9416             return FALSE;
9417         }
9418     } else {
9419         GameListDestroy();
9420         if (fseek(f, 0, 0) == -1) {
9421             if (f == lastLoadGameFP ?
9422                 gameNumber == lastLoadGameNumber + 1 :
9423                 gameNumber == 1) {
9424                 gn = 1;
9425             } else {
9426                 DisplayError(_("Can't seek on game file"), 0);
9427                 return FALSE;
9428             }
9429         }
9430     }
9431     lastLoadGameFP = f;
9432     lastLoadGameNumber = gameNumber;
9433     strcpy(lastLoadGameTitle, title);
9434     lastLoadGameUseList = useList;
9435
9436     yynewfile(f);
9437
9438     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9439       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9440                 lg->gameInfo.black);
9441             DisplayTitle(buf);
9442     } else if (*title != NULLCHAR) {
9443         if (gameNumber > 1) {
9444             sprintf(buf, "%s %d", title, gameNumber);
9445             DisplayTitle(buf);
9446         } else {
9447             DisplayTitle(title);
9448         }
9449     }
9450
9451     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9452         gameMode = PlayFromGameFile;
9453         ModeHighlight();
9454     }
9455
9456     currentMove = forwardMostMove = backwardMostMove = 0;
9457     CopyBoard(boards[0], initialPosition);
9458     StopClocks();
9459
9460     /*
9461      * Skip the first gn-1 games in the file.
9462      * Also skip over anything that precedes an identifiable 
9463      * start of game marker, to avoid being confused by 
9464      * garbage at the start of the file.  Currently 
9465      * recognized start of game markers are the move number "1",
9466      * the pattern "gnuchess .* game", the pattern
9467      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9468      * A game that starts with one of the latter two patterns
9469      * will also have a move number 1, possibly
9470      * following a position diagram.
9471      * 5-4-02: Let's try being more lenient and allowing a game to
9472      * start with an unnumbered move.  Does that break anything?
9473      */
9474     cm = lastLoadGameStart = (ChessMove) 0;
9475     while (gn > 0) {
9476         yyboardindex = forwardMostMove;
9477         cm = (ChessMove) yylex();
9478         switch (cm) {
9479           case (ChessMove) 0:
9480             if (cmailMsgLoaded) {
9481                 nCmailGames = CMAIL_MAX_GAMES - gn;
9482             } else {
9483                 Reset(TRUE, TRUE);
9484                 DisplayError(_("Game not found in file"), 0);
9485             }
9486             return FALSE;
9487
9488           case GNUChessGame:
9489           case XBoardGame:
9490             gn--;
9491             lastLoadGameStart = cm;
9492             break;
9493             
9494           case MoveNumberOne:
9495             switch (lastLoadGameStart) {
9496               case GNUChessGame:
9497               case XBoardGame:
9498               case PGNTag:
9499                 break;
9500               case MoveNumberOne:
9501               case (ChessMove) 0:
9502                 gn--;           /* count this game */
9503                 lastLoadGameStart = cm;
9504                 break;
9505               default:
9506                 /* impossible */
9507                 break;
9508             }
9509             break;
9510
9511           case PGNTag:
9512             switch (lastLoadGameStart) {
9513               case GNUChessGame:
9514               case PGNTag:
9515               case MoveNumberOne:
9516               case (ChessMove) 0:
9517                 gn--;           /* count this game */
9518                 lastLoadGameStart = cm;
9519                 break;
9520               case XBoardGame:
9521                 lastLoadGameStart = cm; /* game counted already */
9522                 break;
9523               default:
9524                 /* impossible */
9525                 break;
9526             }
9527             if (gn > 0) {
9528                 do {
9529                     yyboardindex = forwardMostMove;
9530                     cm = (ChessMove) yylex();
9531                 } while (cm == PGNTag || cm == Comment);
9532             }
9533             break;
9534
9535           case WhiteWins:
9536           case BlackWins:
9537           case GameIsDrawn:
9538             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9539                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9540                     != CMAIL_OLD_RESULT) {
9541                     nCmailResults ++ ;
9542                     cmailResult[  CMAIL_MAX_GAMES
9543                                 - gn - 1] = CMAIL_OLD_RESULT;
9544                 }
9545             }
9546             break;
9547
9548           case NormalMove:
9549             /* Only a NormalMove can be at the start of a game
9550              * without a position diagram. */
9551             if (lastLoadGameStart == (ChessMove) 0) {
9552               gn--;
9553               lastLoadGameStart = MoveNumberOne;
9554             }
9555             break;
9556
9557           default:
9558             break;
9559         }
9560     }
9561     
9562     if (appData.debugMode)
9563       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9564
9565     if (cm == XBoardGame) {
9566         /* Skip any header junk before position diagram and/or move 1 */
9567         for (;;) {
9568             yyboardindex = forwardMostMove;
9569             cm = (ChessMove) yylex();
9570
9571             if (cm == (ChessMove) 0 ||
9572                 cm == GNUChessGame || cm == XBoardGame) {
9573                 /* Empty game; pretend end-of-file and handle later */
9574                 cm = (ChessMove) 0;
9575                 break;
9576             }
9577
9578             if (cm == MoveNumberOne || cm == PositionDiagram ||
9579                 cm == PGNTag || cm == Comment)
9580               break;
9581         }
9582     } else if (cm == GNUChessGame) {
9583         if (gameInfo.event != NULL) {
9584             free(gameInfo.event);
9585         }
9586         gameInfo.event = StrSave(yy_text);
9587     }   
9588
9589     startedFromSetupPosition = FALSE;
9590     while (cm == PGNTag) {
9591         if (appData.debugMode) 
9592           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9593         err = ParsePGNTag(yy_text, &gameInfo);
9594         if (!err) numPGNTags++;
9595
9596         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9597         if(gameInfo.variant != oldVariant) {
9598             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9599             InitPosition(TRUE);
9600             oldVariant = gameInfo.variant;
9601             if (appData.debugMode) 
9602               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9603         }
9604
9605
9606         if (gameInfo.fen != NULL) {
9607           Board initial_position;
9608           startedFromSetupPosition = TRUE;
9609           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9610             Reset(TRUE, TRUE);
9611             DisplayError(_("Bad FEN position in file"), 0);
9612             return FALSE;
9613           }
9614           CopyBoard(boards[0], initial_position);
9615           if (blackPlaysFirst) {
9616             currentMove = forwardMostMove = backwardMostMove = 1;
9617             CopyBoard(boards[1], initial_position);
9618             strcpy(moveList[0], "");
9619             strcpy(parseList[0], "");
9620             timeRemaining[0][1] = whiteTimeRemaining;
9621             timeRemaining[1][1] = blackTimeRemaining;
9622             if (commentList[0] != NULL) {
9623               commentList[1] = commentList[0];
9624               commentList[0] = NULL;
9625             }
9626           } else {
9627             currentMove = forwardMostMove = backwardMostMove = 0;
9628           }
9629           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9630           {   int i;
9631               initialRulePlies = FENrulePlies;
9632               for( i=0; i< nrCastlingRights; i++ )
9633                   initialRights[i] = initial_position[CASTLING][i];
9634           }
9635           yyboardindex = forwardMostMove;
9636           free(gameInfo.fen);
9637           gameInfo.fen = NULL;
9638         }
9639
9640         yyboardindex = forwardMostMove;
9641         cm = (ChessMove) yylex();
9642
9643         /* Handle comments interspersed among the tags */
9644         while (cm == Comment) {
9645             char *p;
9646             if (appData.debugMode) 
9647               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9648             p = yy_text;
9649             AppendComment(currentMove, p, FALSE);
9650             yyboardindex = forwardMostMove;
9651             cm = (ChessMove) yylex();
9652         }
9653     }
9654
9655     /* don't rely on existence of Event tag since if game was
9656      * pasted from clipboard the Event tag may not exist
9657      */
9658     if (numPGNTags > 0){
9659         char *tags;
9660         if (gameInfo.variant == VariantNormal) {
9661           gameInfo.variant = StringToVariant(gameInfo.event);
9662         }
9663         if (!matchMode) {
9664           if( appData.autoDisplayTags ) {
9665             tags = PGNTags(&gameInfo);
9666             TagsPopUp(tags, CmailMsg());
9667             free(tags);
9668           }
9669         }
9670     } else {
9671         /* Make something up, but don't display it now */
9672         SetGameInfo();
9673         TagsPopDown();
9674     }
9675
9676     if (cm == PositionDiagram) {
9677         int i, j;
9678         char *p;
9679         Board initial_position;
9680
9681         if (appData.debugMode)
9682           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9683
9684         if (!startedFromSetupPosition) {
9685             p = yy_text;
9686             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9687               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9688                 switch (*p) {
9689                   case '[':
9690                   case '-':
9691                   case ' ':
9692                   case '\t':
9693                   case '\n':
9694                   case '\r':
9695                     break;
9696                   default:
9697                     initial_position[i][j++] = CharToPiece(*p);
9698                     break;
9699                 }
9700             while (*p == ' ' || *p == '\t' ||
9701                    *p == '\n' || *p == '\r') p++;
9702         
9703             if (strncmp(p, "black", strlen("black"))==0)
9704               blackPlaysFirst = TRUE;
9705             else
9706               blackPlaysFirst = FALSE;
9707             startedFromSetupPosition = TRUE;
9708         
9709             CopyBoard(boards[0], initial_position);
9710             if (blackPlaysFirst) {
9711                 currentMove = forwardMostMove = backwardMostMove = 1;
9712                 CopyBoard(boards[1], initial_position);
9713                 strcpy(moveList[0], "");
9714                 strcpy(parseList[0], "");
9715                 timeRemaining[0][1] = whiteTimeRemaining;
9716                 timeRemaining[1][1] = blackTimeRemaining;
9717                 if (commentList[0] != NULL) {
9718                     commentList[1] = commentList[0];
9719                     commentList[0] = NULL;
9720                 }
9721             } else {
9722                 currentMove = forwardMostMove = backwardMostMove = 0;
9723             }
9724         }
9725         yyboardindex = forwardMostMove;
9726         cm = (ChessMove) yylex();
9727     }
9728
9729     if (first.pr == NoProc) {
9730         StartChessProgram(&first);
9731     }
9732     InitChessProgram(&first, FALSE);
9733     SendToProgram("force\n", &first);
9734     if (startedFromSetupPosition) {
9735         SendBoard(&first, forwardMostMove);
9736     if (appData.debugMode) {
9737         fprintf(debugFP, "Load Game\n");
9738     }
9739         DisplayBothClocks();
9740     }      
9741
9742     /* [HGM] server: flag to write setup moves in broadcast file as one */
9743     loadFlag = appData.suppressLoadMoves;
9744
9745     while (cm == Comment) {
9746         char *p;
9747         if (appData.debugMode) 
9748           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9749         p = yy_text;
9750         AppendComment(currentMove, p, FALSE);
9751         yyboardindex = forwardMostMove;
9752         cm = (ChessMove) yylex();
9753     }
9754
9755     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9756         cm == WhiteWins || cm == BlackWins ||
9757         cm == GameIsDrawn || cm == GameUnfinished) {
9758         DisplayMessage("", _("No moves in game"));
9759         if (cmailMsgLoaded) {
9760             if (appData.debugMode)
9761               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9762             ClearHighlights();
9763             flipView = FALSE;
9764         }
9765         DrawPosition(FALSE, boards[currentMove]);
9766         DisplayBothClocks();
9767         gameMode = EditGame;
9768         ModeHighlight();
9769         gameFileFP = NULL;
9770         cmailOldMove = 0;
9771         return TRUE;
9772     }
9773
9774     // [HGM] PV info: routine tests if comment empty
9775     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9776         DisplayComment(currentMove - 1, commentList[currentMove]);
9777     }
9778     if (!matchMode && appData.timeDelay != 0) 
9779       DrawPosition(FALSE, boards[currentMove]);
9780
9781     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9782       programStats.ok_to_send = 1;
9783     }
9784
9785     /* if the first token after the PGN tags is a move
9786      * and not move number 1, retrieve it from the parser 
9787      */
9788     if (cm != MoveNumberOne)
9789         LoadGameOneMove(cm);
9790
9791     /* load the remaining moves from the file */
9792     while (LoadGameOneMove((ChessMove)0)) {
9793       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9794       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9795     }
9796
9797     /* rewind to the start of the game */
9798     currentMove = backwardMostMove;
9799
9800     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9801
9802     if (oldGameMode == AnalyzeFile ||
9803         oldGameMode == AnalyzeMode) {
9804       AnalyzeFileEvent();
9805     }
9806
9807     if (matchMode || appData.timeDelay == 0) {
9808       ToEndEvent();
9809       gameMode = EditGame;
9810       ModeHighlight();
9811     } else if (appData.timeDelay > 0) {
9812       AutoPlayGameLoop();
9813     }
9814
9815     if (appData.debugMode) 
9816         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9817
9818     loadFlag = 0; /* [HGM] true game starts */
9819     return TRUE;
9820 }
9821
9822 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9823 int
9824 ReloadPosition(offset)
9825      int offset;
9826 {
9827     int positionNumber = lastLoadPositionNumber + offset;
9828     if (lastLoadPositionFP == NULL) {
9829         DisplayError(_("No position has been loaded yet"), 0);
9830         return FALSE;
9831     }
9832     if (positionNumber <= 0) {
9833         DisplayError(_("Can't back up any further"), 0);
9834         return FALSE;
9835     }
9836     return LoadPosition(lastLoadPositionFP, positionNumber,
9837                         lastLoadPositionTitle);
9838 }
9839
9840 /* Load the nth position from the given file */
9841 int
9842 LoadPositionFromFile(filename, n, title)
9843      char *filename;
9844      int n;
9845      char *title;
9846 {
9847     FILE *f;
9848     char buf[MSG_SIZ];
9849
9850     if (strcmp(filename, "-") == 0) {
9851         return LoadPosition(stdin, n, "stdin");
9852     } else {
9853         f = fopen(filename, "rb");
9854         if (f == NULL) {
9855             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9856             DisplayError(buf, errno);
9857             return FALSE;
9858         } else {
9859             return LoadPosition(f, n, title);
9860         }
9861     }
9862 }
9863
9864 /* Load the nth position from the given open file, and close it */
9865 int
9866 LoadPosition(f, positionNumber, title)
9867      FILE *f;
9868      int positionNumber;
9869      char *title;
9870 {
9871     char *p, line[MSG_SIZ];
9872     Board initial_position;
9873     int i, j, fenMode, pn;
9874     
9875     if (gameMode == Training )
9876         SetTrainingModeOff();
9877
9878     if (gameMode != BeginningOfGame) {
9879         Reset(FALSE, TRUE);
9880     }
9881     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9882         fclose(lastLoadPositionFP);
9883     }
9884     if (positionNumber == 0) positionNumber = 1;
9885     lastLoadPositionFP = f;
9886     lastLoadPositionNumber = positionNumber;
9887     strcpy(lastLoadPositionTitle, title);
9888     if (first.pr == NoProc) {
9889       StartChessProgram(&first);
9890       InitChessProgram(&first, FALSE);
9891     }    
9892     pn = positionNumber;
9893     if (positionNumber < 0) {
9894         /* Negative position number means to seek to that byte offset */
9895         if (fseek(f, -positionNumber, 0) == -1) {
9896             DisplayError(_("Can't seek on position file"), 0);
9897             return FALSE;
9898         };
9899         pn = 1;
9900     } else {
9901         if (fseek(f, 0, 0) == -1) {
9902             if (f == lastLoadPositionFP ?
9903                 positionNumber == lastLoadPositionNumber + 1 :
9904                 positionNumber == 1) {
9905                 pn = 1;
9906             } else {
9907                 DisplayError(_("Can't seek on position file"), 0);
9908                 return FALSE;
9909             }
9910         }
9911     }
9912     /* See if this file is FEN or old-style xboard */
9913     if (fgets(line, MSG_SIZ, f) == NULL) {
9914         DisplayError(_("Position not found in file"), 0);
9915         return FALSE;
9916     }
9917     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9918     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9919
9920     if (pn >= 2) {
9921         if (fenMode || line[0] == '#') pn--;
9922         while (pn > 0) {
9923             /* skip positions before number pn */
9924             if (fgets(line, MSG_SIZ, f) == NULL) {
9925                 Reset(TRUE, TRUE);
9926                 DisplayError(_("Position not found in file"), 0);
9927                 return FALSE;
9928             }
9929             if (fenMode || line[0] == '#') pn--;
9930         }
9931     }
9932
9933     if (fenMode) {
9934         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9935             DisplayError(_("Bad FEN position in file"), 0);
9936             return FALSE;
9937         }
9938     } else {
9939         (void) fgets(line, MSG_SIZ, f);
9940         (void) fgets(line, MSG_SIZ, f);
9941     
9942         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9943             (void) fgets(line, MSG_SIZ, f);
9944             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9945                 if (*p == ' ')
9946                   continue;
9947                 initial_position[i][j++] = CharToPiece(*p);
9948             }
9949         }
9950     
9951         blackPlaysFirst = FALSE;
9952         if (!feof(f)) {
9953             (void) fgets(line, MSG_SIZ, f);
9954             if (strncmp(line, "black", strlen("black"))==0)
9955               blackPlaysFirst = TRUE;
9956         }
9957     }
9958     startedFromSetupPosition = TRUE;
9959     
9960     SendToProgram("force\n", &first);
9961     CopyBoard(boards[0], initial_position);
9962     if (blackPlaysFirst) {
9963         currentMove = forwardMostMove = backwardMostMove = 1;
9964         strcpy(moveList[0], "");
9965         strcpy(parseList[0], "");
9966         CopyBoard(boards[1], initial_position);
9967         DisplayMessage("", _("Black to play"));
9968     } else {
9969         currentMove = forwardMostMove = backwardMostMove = 0;
9970         DisplayMessage("", _("White to play"));
9971     }
9972     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9973     SendBoard(&first, forwardMostMove);
9974     if (appData.debugMode) {
9975 int i, j;
9976   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9977   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9978         fprintf(debugFP, "Load Position\n");
9979     }
9980
9981     if (positionNumber > 1) {
9982         sprintf(line, "%s %d", title, positionNumber);
9983         DisplayTitle(line);
9984     } else {
9985         DisplayTitle(title);
9986     }
9987     gameMode = EditGame;
9988     ModeHighlight();
9989     ResetClocks();
9990     timeRemaining[0][1] = whiteTimeRemaining;
9991     timeRemaining[1][1] = blackTimeRemaining;
9992     DrawPosition(FALSE, boards[currentMove]);
9993    
9994     return TRUE;
9995 }
9996
9997
9998 void
9999 CopyPlayerNameIntoFileName(dest, src)
10000      char **dest, *src;
10001 {
10002     while (*src != NULLCHAR && *src != ',') {
10003         if (*src == ' ') {
10004             *(*dest)++ = '_';
10005             src++;
10006         } else {
10007             *(*dest)++ = *src++;
10008         }
10009     }
10010 }
10011
10012 char *DefaultFileName(ext)
10013      char *ext;
10014 {
10015     static char def[MSG_SIZ];
10016     char *p;
10017
10018     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10019         p = def;
10020         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10021         *p++ = '-';
10022         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10023         *p++ = '.';
10024         strcpy(p, ext);
10025     } else {
10026         def[0] = NULLCHAR;
10027     }
10028     return def;
10029 }
10030
10031 /* Save the current game to the given file */
10032 int
10033 SaveGameToFile(filename, append)
10034      char *filename;
10035      int append;
10036 {
10037     FILE *f;
10038     char buf[MSG_SIZ];
10039
10040     if (strcmp(filename, "-") == 0) {
10041         return SaveGame(stdout, 0, NULL);
10042     } else {
10043         f = fopen(filename, append ? "a" : "w");
10044         if (f == NULL) {
10045             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10046             DisplayError(buf, errno);
10047             return FALSE;
10048         } else {
10049             return SaveGame(f, 0, NULL);
10050         }
10051     }
10052 }
10053
10054 char *
10055 SavePart(str)
10056      char *str;
10057 {
10058     static char buf[MSG_SIZ];
10059     char *p;
10060     
10061     p = strchr(str, ' ');
10062     if (p == NULL) return str;
10063     strncpy(buf, str, p - str);
10064     buf[p - str] = NULLCHAR;
10065     return buf;
10066 }
10067
10068 #define PGN_MAX_LINE 75
10069
10070 #define PGN_SIDE_WHITE  0
10071 #define PGN_SIDE_BLACK  1
10072
10073 /* [AS] */
10074 static int FindFirstMoveOutOfBook( int side )
10075 {
10076     int result = -1;
10077
10078     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10079         int index = backwardMostMove;
10080         int has_book_hit = 0;
10081
10082         if( (index % 2) != side ) {
10083             index++;
10084         }
10085
10086         while( index < forwardMostMove ) {
10087             /* Check to see if engine is in book */
10088             int depth = pvInfoList[index].depth;
10089             int score = pvInfoList[index].score;
10090             int in_book = 0;
10091
10092             if( depth <= 2 ) {
10093                 in_book = 1;
10094             }
10095             else if( score == 0 && depth == 63 ) {
10096                 in_book = 1; /* Zappa */
10097             }
10098             else if( score == 2 && depth == 99 ) {
10099                 in_book = 1; /* Abrok */
10100             }
10101
10102             has_book_hit += in_book;
10103
10104             if( ! in_book ) {
10105                 result = index;
10106
10107                 break;
10108             }
10109
10110             index += 2;
10111         }
10112     }
10113
10114     return result;
10115 }
10116
10117 /* [AS] */
10118 void GetOutOfBookInfo( char * buf )
10119 {
10120     int oob[2];
10121     int i;
10122     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10123
10124     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10125     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10126
10127     *buf = '\0';
10128
10129     if( oob[0] >= 0 || oob[1] >= 0 ) {
10130         for( i=0; i<2; i++ ) {
10131             int idx = oob[i];
10132
10133             if( idx >= 0 ) {
10134                 if( i > 0 && oob[0] >= 0 ) {
10135                     strcat( buf, "   " );
10136                 }
10137
10138                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10139                 sprintf( buf+strlen(buf), "%s%.2f", 
10140                     pvInfoList[idx].score >= 0 ? "+" : "",
10141                     pvInfoList[idx].score / 100.0 );
10142             }
10143         }
10144     }
10145 }
10146
10147 /* Save game in PGN style and close the file */
10148 int
10149 SaveGamePGN(f)
10150      FILE *f;
10151 {
10152     int i, offset, linelen, newblock;
10153     time_t tm;
10154 //    char *movetext;
10155     char numtext[32];
10156     int movelen, numlen, blank;
10157     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10158
10159     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10160     
10161     tm = time((time_t *) NULL);
10162     
10163     PrintPGNTags(f, &gameInfo);
10164     
10165     if (backwardMostMove > 0 || startedFromSetupPosition) {
10166         char *fen = PositionToFEN(backwardMostMove, NULL);
10167         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10168         fprintf(f, "\n{--------------\n");
10169         PrintPosition(f, backwardMostMove);
10170         fprintf(f, "--------------}\n");
10171         free(fen);
10172     }
10173     else {
10174         /* [AS] Out of book annotation */
10175         if( appData.saveOutOfBookInfo ) {
10176             char buf[64];
10177
10178             GetOutOfBookInfo( buf );
10179
10180             if( buf[0] != '\0' ) {
10181                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10182             }
10183         }
10184
10185         fprintf(f, "\n");
10186     }
10187
10188     i = backwardMostMove;
10189     linelen = 0;
10190     newblock = TRUE;
10191
10192     while (i < forwardMostMove) {
10193         /* Print comments preceding this move */
10194         if (commentList[i] != NULL) {
10195             if (linelen > 0) fprintf(f, "\n");
10196             fprintf(f, "%s", commentList[i]);
10197             linelen = 0;
10198             newblock = TRUE;
10199         }
10200
10201         /* Format move number */
10202         if ((i % 2) == 0) {
10203             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10204         } else {
10205             if (newblock) {
10206                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10207             } else {
10208                 numtext[0] = NULLCHAR;
10209             }
10210         }
10211         numlen = strlen(numtext);
10212         newblock = FALSE;
10213
10214         /* Print move number */
10215         blank = linelen > 0 && numlen > 0;
10216         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10217             fprintf(f, "\n");
10218             linelen = 0;
10219             blank = 0;
10220         }
10221         if (blank) {
10222             fprintf(f, " ");
10223             linelen++;
10224         }
10225         fprintf(f, "%s", numtext);
10226         linelen += numlen;
10227
10228         /* Get move */
10229         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10230         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10231
10232         /* Print move */
10233         blank = linelen > 0 && movelen > 0;
10234         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10235             fprintf(f, "\n");
10236             linelen = 0;
10237             blank = 0;
10238         }
10239         if (blank) {
10240             fprintf(f, " ");
10241             linelen++;
10242         }
10243         fprintf(f, "%s", move_buffer);
10244         linelen += movelen;
10245
10246         /* [AS] Add PV info if present */
10247         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10248             /* [HGM] add time */
10249             char buf[MSG_SIZ]; int seconds;
10250
10251             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10252
10253             if( seconds <= 0) buf[0] = 0; else
10254             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10255                 seconds = (seconds + 4)/10; // round to full seconds
10256                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10257                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10258             }
10259
10260             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10261                 pvInfoList[i].score >= 0 ? "+" : "",
10262                 pvInfoList[i].score / 100.0,
10263                 pvInfoList[i].depth,
10264                 buf );
10265
10266             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10267
10268             /* Print score/depth */
10269             blank = linelen > 0 && movelen > 0;
10270             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10271                 fprintf(f, "\n");
10272                 linelen = 0;
10273                 blank = 0;
10274             }
10275             if (blank) {
10276                 fprintf(f, " ");
10277                 linelen++;
10278             }
10279             fprintf(f, "%s", move_buffer);
10280             linelen += movelen;
10281         }
10282
10283         i++;
10284     }
10285     
10286     /* Start a new line */
10287     if (linelen > 0) fprintf(f, "\n");
10288
10289     /* Print comments after last move */
10290     if (commentList[i] != NULL) {
10291         fprintf(f, "%s\n", commentList[i]);
10292     }
10293
10294     /* Print result */
10295     if (gameInfo.resultDetails != NULL &&
10296         gameInfo.resultDetails[0] != NULLCHAR) {
10297         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10298                 PGNResult(gameInfo.result));
10299     } else {
10300         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10301     }
10302
10303     fclose(f);
10304     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10305     return TRUE;
10306 }
10307
10308 /* Save game in old style and close the file */
10309 int
10310 SaveGameOldStyle(f)
10311      FILE *f;
10312 {
10313     int i, offset;
10314     time_t tm;
10315     
10316     tm = time((time_t *) NULL);
10317     
10318     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10319     PrintOpponents(f);
10320     
10321     if (backwardMostMove > 0 || startedFromSetupPosition) {
10322         fprintf(f, "\n[--------------\n");
10323         PrintPosition(f, backwardMostMove);
10324         fprintf(f, "--------------]\n");
10325     } else {
10326         fprintf(f, "\n");
10327     }
10328
10329     i = backwardMostMove;
10330     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10331
10332     while (i < forwardMostMove) {
10333         if (commentList[i] != NULL) {
10334             fprintf(f, "[%s]\n", commentList[i]);
10335         }
10336
10337         if ((i % 2) == 1) {
10338             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10339             i++;
10340         } else {
10341             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10342             i++;
10343             if (commentList[i] != NULL) {
10344                 fprintf(f, "\n");
10345                 continue;
10346             }
10347             if (i >= forwardMostMove) {
10348                 fprintf(f, "\n");
10349                 break;
10350             }
10351             fprintf(f, "%s\n", parseList[i]);
10352             i++;
10353         }
10354     }
10355     
10356     if (commentList[i] != NULL) {
10357         fprintf(f, "[%s]\n", commentList[i]);
10358     }
10359
10360     /* This isn't really the old style, but it's close enough */
10361     if (gameInfo.resultDetails != NULL &&
10362         gameInfo.resultDetails[0] != NULLCHAR) {
10363         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10364                 gameInfo.resultDetails);
10365     } else {
10366         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10367     }
10368
10369     fclose(f);
10370     return TRUE;
10371 }
10372
10373 /* Save the current game to open file f and close the file */
10374 int
10375 SaveGame(f, dummy, dummy2)
10376      FILE *f;
10377      int dummy;
10378      char *dummy2;
10379 {
10380     if (gameMode == EditPosition) EditPositionDone(TRUE);
10381     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10382     if (appData.oldSaveStyle)
10383       return SaveGameOldStyle(f);
10384     else
10385       return SaveGamePGN(f);
10386 }
10387
10388 /* Save the current position to the given file */
10389 int
10390 SavePositionToFile(filename)
10391      char *filename;
10392 {
10393     FILE *f;
10394     char buf[MSG_SIZ];
10395
10396     if (strcmp(filename, "-") == 0) {
10397         return SavePosition(stdout, 0, NULL);
10398     } else {
10399         f = fopen(filename, "a");
10400         if (f == NULL) {
10401             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10402             DisplayError(buf, errno);
10403             return FALSE;
10404         } else {
10405             SavePosition(f, 0, NULL);
10406             return TRUE;
10407         }
10408     }
10409 }
10410
10411 /* Save the current position to the given open file and close the file */
10412 int
10413 SavePosition(f, dummy, dummy2)
10414      FILE *f;
10415      int dummy;
10416      char *dummy2;
10417 {
10418     time_t tm;
10419     char *fen;
10420     
10421     if (gameMode == EditPosition) EditPositionDone(TRUE);
10422     if (appData.oldSaveStyle) {
10423         tm = time((time_t *) NULL);
10424     
10425         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10426         PrintOpponents(f);
10427         fprintf(f, "[--------------\n");
10428         PrintPosition(f, currentMove);
10429         fprintf(f, "--------------]\n");
10430     } else {
10431         fen = PositionToFEN(currentMove, NULL);
10432         fprintf(f, "%s\n", fen);
10433         free(fen);
10434     }
10435     fclose(f);
10436     return TRUE;
10437 }
10438
10439 void
10440 ReloadCmailMsgEvent(unregister)
10441      int unregister;
10442 {
10443 #if !WIN32
10444     static char *inFilename = NULL;
10445     static char *outFilename;
10446     int i;
10447     struct stat inbuf, outbuf;
10448     int status;
10449     
10450     /* Any registered moves are unregistered if unregister is set, */
10451     /* i.e. invoked by the signal handler */
10452     if (unregister) {
10453         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10454             cmailMoveRegistered[i] = FALSE;
10455             if (cmailCommentList[i] != NULL) {
10456                 free(cmailCommentList[i]);
10457                 cmailCommentList[i] = NULL;
10458             }
10459         }
10460         nCmailMovesRegistered = 0;
10461     }
10462
10463     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10464         cmailResult[i] = CMAIL_NOT_RESULT;
10465     }
10466     nCmailResults = 0;
10467
10468     if (inFilename == NULL) {
10469         /* Because the filenames are static they only get malloced once  */
10470         /* and they never get freed                                      */
10471         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10472         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10473
10474         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10475         sprintf(outFilename, "%s.out", appData.cmailGameName);
10476     }
10477     
10478     status = stat(outFilename, &outbuf);
10479     if (status < 0) {
10480         cmailMailedMove = FALSE;
10481     } else {
10482         status = stat(inFilename, &inbuf);
10483         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10484     }
10485     
10486     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10487        counts the games, notes how each one terminated, etc.
10488        
10489        It would be nice to remove this kludge and instead gather all
10490        the information while building the game list.  (And to keep it
10491        in the game list nodes instead of having a bunch of fixed-size
10492        parallel arrays.)  Note this will require getting each game's
10493        termination from the PGN tags, as the game list builder does
10494        not process the game moves.  --mann
10495        */
10496     cmailMsgLoaded = TRUE;
10497     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10498     
10499     /* Load first game in the file or popup game menu */
10500     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10501
10502 #endif /* !WIN32 */
10503     return;
10504 }
10505
10506 int
10507 RegisterMove()
10508 {
10509     FILE *f;
10510     char string[MSG_SIZ];
10511
10512     if (   cmailMailedMove
10513         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10514         return TRUE;            /* Allow free viewing  */
10515     }
10516
10517     /* Unregister move to ensure that we don't leave RegisterMove        */
10518     /* with the move registered when the conditions for registering no   */
10519     /* longer hold                                                       */
10520     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10521         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10522         nCmailMovesRegistered --;
10523
10524         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10525           {
10526               free(cmailCommentList[lastLoadGameNumber - 1]);
10527               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10528           }
10529     }
10530
10531     if (cmailOldMove == -1) {
10532         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10533         return FALSE;
10534     }
10535
10536     if (currentMove > cmailOldMove + 1) {
10537         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10538         return FALSE;
10539     }
10540
10541     if (currentMove < cmailOldMove) {
10542         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10543         return FALSE;
10544     }
10545
10546     if (forwardMostMove > currentMove) {
10547         /* Silently truncate extra moves */
10548         TruncateGame();
10549     }
10550
10551     if (   (currentMove == cmailOldMove + 1)
10552         || (   (currentMove == cmailOldMove)
10553             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10554                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10555         if (gameInfo.result != GameUnfinished) {
10556             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10557         }
10558
10559         if (commentList[currentMove] != NULL) {
10560             cmailCommentList[lastLoadGameNumber - 1]
10561               = StrSave(commentList[currentMove]);
10562         }
10563         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10564
10565         if (appData.debugMode)
10566           fprintf(debugFP, "Saving %s for game %d\n",
10567                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10568
10569         sprintf(string,
10570                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10571         
10572         f = fopen(string, "w");
10573         if (appData.oldSaveStyle) {
10574             SaveGameOldStyle(f); /* also closes the file */
10575             
10576             sprintf(string, "%s.pos.out", appData.cmailGameName);
10577             f = fopen(string, "w");
10578             SavePosition(f, 0, NULL); /* also closes the file */
10579         } else {
10580             fprintf(f, "{--------------\n");
10581             PrintPosition(f, currentMove);
10582             fprintf(f, "--------------}\n\n");
10583             
10584             SaveGame(f, 0, NULL); /* also closes the file*/
10585         }
10586         
10587         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10588         nCmailMovesRegistered ++;
10589     } else if (nCmailGames == 1) {
10590         DisplayError(_("You have not made a move yet"), 0);
10591         return FALSE;
10592     }
10593
10594     return TRUE;
10595 }
10596
10597 void
10598 MailMoveEvent()
10599 {
10600 #if !WIN32
10601     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10602     FILE *commandOutput;
10603     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10604     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10605     int nBuffers;
10606     int i;
10607     int archived;
10608     char *arcDir;
10609
10610     if (! cmailMsgLoaded) {
10611         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10612         return;
10613     }
10614
10615     if (nCmailGames == nCmailResults) {
10616         DisplayError(_("No unfinished games"), 0);
10617         return;
10618     }
10619
10620 #if CMAIL_PROHIBIT_REMAIL
10621     if (cmailMailedMove) {
10622         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);
10623         DisplayError(msg, 0);
10624         return;
10625     }
10626 #endif
10627
10628     if (! (cmailMailedMove || RegisterMove())) return;
10629     
10630     if (   cmailMailedMove
10631         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10632         sprintf(string, partCommandString,
10633                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10634         commandOutput = popen(string, "r");
10635
10636         if (commandOutput == NULL) {
10637             DisplayError(_("Failed to invoke cmail"), 0);
10638         } else {
10639             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10640                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10641             }
10642             if (nBuffers > 1) {
10643                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10644                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10645                 nBytes = MSG_SIZ - 1;
10646             } else {
10647                 (void) memcpy(msg, buffer, nBytes);
10648             }
10649             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10650
10651             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10652                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10653
10654                 archived = TRUE;
10655                 for (i = 0; i < nCmailGames; i ++) {
10656                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10657                         archived = FALSE;
10658                     }
10659                 }
10660                 if (   archived
10661                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10662                         != NULL)) {
10663                     sprintf(buffer, "%s/%s.%s.archive",
10664                             arcDir,
10665                             appData.cmailGameName,
10666                             gameInfo.date);
10667                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10668                     cmailMsgLoaded = FALSE;
10669                 }
10670             }
10671
10672             DisplayInformation(msg);
10673             pclose(commandOutput);
10674         }
10675     } else {
10676         if ((*cmailMsg) != '\0') {
10677             DisplayInformation(cmailMsg);
10678         }
10679     }
10680
10681     return;
10682 #endif /* !WIN32 */
10683 }
10684
10685 char *
10686 CmailMsg()
10687 {
10688 #if WIN32
10689     return NULL;
10690 #else
10691     int  prependComma = 0;
10692     char number[5];
10693     char string[MSG_SIZ];       /* Space for game-list */
10694     int  i;
10695     
10696     if (!cmailMsgLoaded) return "";
10697
10698     if (cmailMailedMove) {
10699         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10700     } else {
10701         /* Create a list of games left */
10702         sprintf(string, "[");
10703         for (i = 0; i < nCmailGames; i ++) {
10704             if (! (   cmailMoveRegistered[i]
10705                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10706                 if (prependComma) {
10707                     sprintf(number, ",%d", i + 1);
10708                 } else {
10709                     sprintf(number, "%d", i + 1);
10710                     prependComma = 1;
10711                 }
10712                 
10713                 strcat(string, number);
10714             }
10715         }
10716         strcat(string, "]");
10717
10718         if (nCmailMovesRegistered + nCmailResults == 0) {
10719             switch (nCmailGames) {
10720               case 1:
10721                 sprintf(cmailMsg,
10722                         _("Still need to make move for game\n"));
10723                 break;
10724                 
10725               case 2:
10726                 sprintf(cmailMsg,
10727                         _("Still need to make moves for both games\n"));
10728                 break;
10729                 
10730               default:
10731                 sprintf(cmailMsg,
10732                         _("Still need to make moves for all %d games\n"),
10733                         nCmailGames);
10734                 break;
10735             }
10736         } else {
10737             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10738               case 1:
10739                 sprintf(cmailMsg,
10740                         _("Still need to make a move for game %s\n"),
10741                         string);
10742                 break;
10743                 
10744               case 0:
10745                 if (nCmailResults == nCmailGames) {
10746                     sprintf(cmailMsg, _("No unfinished games\n"));
10747                 } else {
10748                     sprintf(cmailMsg, _("Ready to send mail\n"));
10749                 }
10750                 break;
10751                 
10752               default:
10753                 sprintf(cmailMsg,
10754                         _("Still need to make moves for games %s\n"),
10755                         string);
10756             }
10757         }
10758     }
10759     return cmailMsg;
10760 #endif /* WIN32 */
10761 }
10762
10763 void
10764 ResetGameEvent()
10765 {
10766     if (gameMode == Training)
10767       SetTrainingModeOff();
10768
10769     Reset(TRUE, TRUE);
10770     cmailMsgLoaded = FALSE;
10771     if (appData.icsActive) {
10772       SendToICS(ics_prefix);
10773       SendToICS("refresh\n");
10774     }
10775 }
10776
10777 void
10778 ExitEvent(status)
10779      int status;
10780 {
10781     exiting++;
10782     if (exiting > 2) {
10783       /* Give up on clean exit */
10784       exit(status);
10785     }
10786     if (exiting > 1) {
10787       /* Keep trying for clean exit */
10788       return;
10789     }
10790
10791     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10792
10793     if (telnetISR != NULL) {
10794       RemoveInputSource(telnetISR);
10795     }
10796     if (icsPR != NoProc) {
10797       DestroyChildProcess(icsPR, TRUE);
10798     }
10799
10800     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10801     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10802
10803     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10804     /* make sure this other one finishes before killing it!                  */
10805     if(endingGame) { int count = 0;
10806         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10807         while(endingGame && count++ < 10) DoSleep(1);
10808         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10809     }
10810
10811     /* Kill off chess programs */
10812     if (first.pr != NoProc) {
10813         ExitAnalyzeMode();
10814         
10815         DoSleep( appData.delayBeforeQuit );
10816         SendToProgram("quit\n", &first);
10817         DoSleep( appData.delayAfterQuit );
10818         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10819     }
10820     if (second.pr != NoProc) {
10821         DoSleep( appData.delayBeforeQuit );
10822         SendToProgram("quit\n", &second);
10823         DoSleep( appData.delayAfterQuit );
10824         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10825     }
10826     if (first.isr != NULL) {
10827         RemoveInputSource(first.isr);
10828     }
10829     if (second.isr != NULL) {
10830         RemoveInputSource(second.isr);
10831     }
10832
10833     ShutDownFrontEnd();
10834     exit(status);
10835 }
10836
10837 void
10838 PauseEvent()
10839 {
10840     if (appData.debugMode)
10841         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10842     if (pausing) {
10843         pausing = FALSE;
10844         ModeHighlight();
10845         if (gameMode == MachinePlaysWhite ||
10846             gameMode == MachinePlaysBlack) {
10847             StartClocks();
10848         } else {
10849             DisplayBothClocks();
10850         }
10851         if (gameMode == PlayFromGameFile) {
10852             if (appData.timeDelay >= 0) 
10853                 AutoPlayGameLoop();
10854         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10855             Reset(FALSE, TRUE);
10856             SendToICS(ics_prefix);
10857             SendToICS("refresh\n");
10858         } else if (currentMove < forwardMostMove) {
10859             ForwardInner(forwardMostMove);
10860         }
10861         pauseExamInvalid = FALSE;
10862     } else {
10863         switch (gameMode) {
10864           default:
10865             return;
10866           case IcsExamining:
10867             pauseExamForwardMostMove = forwardMostMove;
10868             pauseExamInvalid = FALSE;
10869             /* fall through */
10870           case IcsObserving:
10871           case IcsPlayingWhite:
10872           case IcsPlayingBlack:
10873             pausing = TRUE;
10874             ModeHighlight();
10875             return;
10876           case PlayFromGameFile:
10877             (void) StopLoadGameTimer();
10878             pausing = TRUE;
10879             ModeHighlight();
10880             break;
10881           case BeginningOfGame:
10882             if (appData.icsActive) return;
10883             /* else fall through */
10884           case MachinePlaysWhite:
10885           case MachinePlaysBlack:
10886           case TwoMachinesPlay:
10887             if (forwardMostMove == 0)
10888               return;           /* don't pause if no one has moved */
10889             if ((gameMode == MachinePlaysWhite &&
10890                  !WhiteOnMove(forwardMostMove)) ||
10891                 (gameMode == MachinePlaysBlack &&
10892                  WhiteOnMove(forwardMostMove))) {
10893                 StopClocks();
10894             }
10895             pausing = TRUE;
10896             ModeHighlight();
10897             break;
10898         }
10899     }
10900 }
10901
10902 void
10903 EditCommentEvent()
10904 {
10905     char title[MSG_SIZ];
10906
10907     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10908         strcpy(title, _("Edit comment"));
10909     } else {
10910         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10911                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10912                 parseList[currentMove - 1]);
10913     }
10914
10915     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10916 }
10917
10918
10919 void
10920 EditTagsEvent()
10921 {
10922     char *tags = PGNTags(&gameInfo);
10923     EditTagsPopUp(tags);
10924     free(tags);
10925 }
10926
10927 void
10928 AnalyzeModeEvent()
10929 {
10930     if (appData.noChessProgram || gameMode == AnalyzeMode)
10931       return;
10932
10933     if (gameMode != AnalyzeFile) {
10934         if (!appData.icsEngineAnalyze) {
10935                EditGameEvent();
10936                if (gameMode != EditGame) return;
10937         }
10938         ResurrectChessProgram();
10939         SendToProgram("analyze\n", &first);
10940         first.analyzing = TRUE;
10941         /*first.maybeThinking = TRUE;*/
10942         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10943         EngineOutputPopUp();
10944     }
10945     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10946     pausing = FALSE;
10947     ModeHighlight();
10948     SetGameInfo();
10949
10950     StartAnalysisClock();
10951     GetTimeMark(&lastNodeCountTime);
10952     lastNodeCount = 0;
10953 }
10954
10955 void
10956 AnalyzeFileEvent()
10957 {
10958     if (appData.noChessProgram || gameMode == AnalyzeFile)
10959       return;
10960
10961     if (gameMode != AnalyzeMode) {
10962         EditGameEvent();
10963         if (gameMode != EditGame) return;
10964         ResurrectChessProgram();
10965         SendToProgram("analyze\n", &first);
10966         first.analyzing = TRUE;
10967         /*first.maybeThinking = TRUE;*/
10968         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10969         EngineOutputPopUp();
10970     }
10971     gameMode = AnalyzeFile;
10972     pausing = FALSE;
10973     ModeHighlight();
10974     SetGameInfo();
10975
10976     StartAnalysisClock();
10977     GetTimeMark(&lastNodeCountTime);
10978     lastNodeCount = 0;
10979 }
10980
10981 void
10982 MachineWhiteEvent()
10983 {
10984     char buf[MSG_SIZ];
10985     char *bookHit = NULL;
10986
10987     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10988       return;
10989
10990
10991     if (gameMode == PlayFromGameFile || 
10992         gameMode == TwoMachinesPlay  || 
10993         gameMode == Training         || 
10994         gameMode == AnalyzeMode      || 
10995         gameMode == EndOfGame)
10996         EditGameEvent();
10997
10998     if (gameMode == EditPosition) 
10999         EditPositionDone(TRUE);
11000
11001     if (!WhiteOnMove(currentMove)) {
11002         DisplayError(_("It is not White's turn"), 0);
11003         return;
11004     }
11005   
11006     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11007       ExitAnalyzeMode();
11008
11009     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11010         gameMode == AnalyzeFile)
11011         TruncateGame();
11012
11013     ResurrectChessProgram();    /* in case it isn't running */
11014     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11015         gameMode = MachinePlaysWhite;
11016         ResetClocks();
11017     } else
11018     gameMode = MachinePlaysWhite;
11019     pausing = FALSE;
11020     ModeHighlight();
11021     SetGameInfo();
11022     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11023     DisplayTitle(buf);
11024     if (first.sendName) {
11025       sprintf(buf, "name %s\n", gameInfo.black);
11026       SendToProgram(buf, &first);
11027     }
11028     if (first.sendTime) {
11029       if (first.useColors) {
11030         SendToProgram("black\n", &first); /*gnu kludge*/
11031       }
11032       SendTimeRemaining(&first, TRUE);
11033     }
11034     if (first.useColors) {
11035       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11036     }
11037     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11038     SetMachineThinkingEnables();
11039     first.maybeThinking = TRUE;
11040     StartClocks();
11041     firstMove = FALSE;
11042
11043     if (appData.autoFlipView && !flipView) {
11044       flipView = !flipView;
11045       DrawPosition(FALSE, NULL);
11046       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11047     }
11048
11049     if(bookHit) { // [HGM] book: simulate book reply
11050         static char bookMove[MSG_SIZ]; // a bit generous?
11051
11052         programStats.nodes = programStats.depth = programStats.time = 
11053         programStats.score = programStats.got_only_move = 0;
11054         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11055
11056         strcpy(bookMove, "move ");
11057         strcat(bookMove, bookHit);
11058         HandleMachineMove(bookMove, &first);
11059     }
11060 }
11061
11062 void
11063 MachineBlackEvent()
11064 {
11065     char buf[MSG_SIZ];
11066    char *bookHit = NULL;
11067
11068     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11069         return;
11070
11071
11072     if (gameMode == PlayFromGameFile || 
11073         gameMode == TwoMachinesPlay  || 
11074         gameMode == Training         || 
11075         gameMode == AnalyzeMode      || 
11076         gameMode == EndOfGame)
11077         EditGameEvent();
11078
11079     if (gameMode == EditPosition) 
11080         EditPositionDone(TRUE);
11081
11082     if (WhiteOnMove(currentMove)) {
11083         DisplayError(_("It is not Black's turn"), 0);
11084         return;
11085     }
11086     
11087     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11088       ExitAnalyzeMode();
11089
11090     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11091         gameMode == AnalyzeFile)
11092         TruncateGame();
11093
11094     ResurrectChessProgram();    /* in case it isn't running */
11095     gameMode = MachinePlaysBlack;
11096     pausing = FALSE;
11097     ModeHighlight();
11098     SetGameInfo();
11099     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11100     DisplayTitle(buf);
11101     if (first.sendName) {
11102       sprintf(buf, "name %s\n", gameInfo.white);
11103       SendToProgram(buf, &first);
11104     }
11105     if (first.sendTime) {
11106       if (first.useColors) {
11107         SendToProgram("white\n", &first); /*gnu kludge*/
11108       }
11109       SendTimeRemaining(&first, FALSE);
11110     }
11111     if (first.useColors) {
11112       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11113     }
11114     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11115     SetMachineThinkingEnables();
11116     first.maybeThinking = TRUE;
11117     StartClocks();
11118
11119     if (appData.autoFlipView && flipView) {
11120       flipView = !flipView;
11121       DrawPosition(FALSE, NULL);
11122       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11123     }
11124     if(bookHit) { // [HGM] book: simulate book reply
11125         static char bookMove[MSG_SIZ]; // a bit generous?
11126
11127         programStats.nodes = programStats.depth = programStats.time = 
11128         programStats.score = programStats.got_only_move = 0;
11129         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11130
11131         strcpy(bookMove, "move ");
11132         strcat(bookMove, bookHit);
11133         HandleMachineMove(bookMove, &first);
11134     }
11135 }
11136
11137
11138 void
11139 DisplayTwoMachinesTitle()
11140 {
11141     char buf[MSG_SIZ];
11142     if (appData.matchGames > 0) {
11143         if (first.twoMachinesColor[0] == 'w') {
11144             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11145                     gameInfo.white, gameInfo.black,
11146                     first.matchWins, second.matchWins,
11147                     matchGame - 1 - (first.matchWins + second.matchWins));
11148         } else {
11149             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11150                     gameInfo.white, gameInfo.black,
11151                     second.matchWins, first.matchWins,
11152                     matchGame - 1 - (first.matchWins + second.matchWins));
11153         }
11154     } else {
11155         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11156     }
11157     DisplayTitle(buf);
11158 }
11159
11160 void
11161 TwoMachinesEvent P((void))
11162 {
11163     int i;
11164     char buf[MSG_SIZ];
11165     ChessProgramState *onmove;
11166     char *bookHit = NULL;
11167     
11168     if (appData.noChessProgram) return;
11169
11170     switch (gameMode) {
11171       case TwoMachinesPlay:
11172         return;
11173       case MachinePlaysWhite:
11174       case MachinePlaysBlack:
11175         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11176             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11177             return;
11178         }
11179         /* fall through */
11180       case BeginningOfGame:
11181       case PlayFromGameFile:
11182       case EndOfGame:
11183         EditGameEvent();
11184         if (gameMode != EditGame) return;
11185         break;
11186       case EditPosition:
11187         EditPositionDone(TRUE);
11188         break;
11189       case AnalyzeMode:
11190       case AnalyzeFile:
11191         ExitAnalyzeMode();
11192         break;
11193       case EditGame:
11194       default:
11195         break;
11196     }
11197
11198 //    forwardMostMove = currentMove;
11199     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11200     ResurrectChessProgram();    /* in case first program isn't running */
11201
11202     if (second.pr == NULL) {
11203         StartChessProgram(&second);
11204         if (second.protocolVersion == 1) {
11205           TwoMachinesEventIfReady();
11206         } else {
11207           /* kludge: allow timeout for initial "feature" command */
11208           FreezeUI();
11209           DisplayMessage("", _("Starting second chess program"));
11210           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11211         }
11212         return;
11213     }
11214     DisplayMessage("", "");
11215     InitChessProgram(&second, FALSE);
11216     SendToProgram("force\n", &second);
11217     if (startedFromSetupPosition) {
11218         SendBoard(&second, backwardMostMove);
11219     if (appData.debugMode) {
11220         fprintf(debugFP, "Two Machines\n");
11221     }
11222     }
11223     for (i = backwardMostMove; i < forwardMostMove; i++) {
11224         SendMoveToProgram(i, &second);
11225     }
11226
11227     gameMode = TwoMachinesPlay;
11228     pausing = FALSE;
11229     ModeHighlight();
11230     SetGameInfo();
11231     DisplayTwoMachinesTitle();
11232     firstMove = TRUE;
11233     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11234         onmove = &first;
11235     } else {
11236         onmove = &second;
11237     }
11238
11239     SendToProgram(first.computerString, &first);
11240     if (first.sendName) {
11241       sprintf(buf, "name %s\n", second.tidy);
11242       SendToProgram(buf, &first);
11243     }
11244     SendToProgram(second.computerString, &second);
11245     if (second.sendName) {
11246       sprintf(buf, "name %s\n", first.tidy);
11247       SendToProgram(buf, &second);
11248     }
11249
11250     ResetClocks();
11251     if (!first.sendTime || !second.sendTime) {
11252         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11253         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11254     }
11255     if (onmove->sendTime) {
11256       if (onmove->useColors) {
11257         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11258       }
11259       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11260     }
11261     if (onmove->useColors) {
11262       SendToProgram(onmove->twoMachinesColor, onmove);
11263     }
11264     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11265 //    SendToProgram("go\n", onmove);
11266     onmove->maybeThinking = TRUE;
11267     SetMachineThinkingEnables();
11268
11269     StartClocks();
11270
11271     if(bookHit) { // [HGM] book: simulate book reply
11272         static char bookMove[MSG_SIZ]; // a bit generous?
11273
11274         programStats.nodes = programStats.depth = programStats.time = 
11275         programStats.score = programStats.got_only_move = 0;
11276         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11277
11278         strcpy(bookMove, "move ");
11279         strcat(bookMove, bookHit);
11280         savedMessage = bookMove; // args for deferred call
11281         savedState = onmove;
11282         ScheduleDelayedEvent(DeferredBookMove, 1);
11283     }
11284 }
11285
11286 void
11287 TrainingEvent()
11288 {
11289     if (gameMode == Training) {
11290       SetTrainingModeOff();
11291       gameMode = PlayFromGameFile;
11292       DisplayMessage("", _("Training mode off"));
11293     } else {
11294       gameMode = Training;
11295       animateTraining = appData.animate;
11296
11297       /* make sure we are not already at the end of the game */
11298       if (currentMove < forwardMostMove) {
11299         SetTrainingModeOn();
11300         DisplayMessage("", _("Training mode on"));
11301       } else {
11302         gameMode = PlayFromGameFile;
11303         DisplayError(_("Already at end of game"), 0);
11304       }
11305     }
11306     ModeHighlight();
11307 }
11308
11309 void
11310 IcsClientEvent()
11311 {
11312     if (!appData.icsActive) return;
11313     switch (gameMode) {
11314       case IcsPlayingWhite:
11315       case IcsPlayingBlack:
11316       case IcsObserving:
11317       case IcsIdle:
11318       case BeginningOfGame:
11319       case IcsExamining:
11320         return;
11321
11322       case EditGame:
11323         break;
11324
11325       case EditPosition:
11326         EditPositionDone(TRUE);
11327         break;
11328
11329       case AnalyzeMode:
11330       case AnalyzeFile:
11331         ExitAnalyzeMode();
11332         break;
11333         
11334       default:
11335         EditGameEvent();
11336         break;
11337     }
11338
11339     gameMode = IcsIdle;
11340     ModeHighlight();
11341     return;
11342 }
11343
11344
11345 void
11346 EditGameEvent()
11347 {
11348     int i;
11349
11350     switch (gameMode) {
11351       case Training:
11352         SetTrainingModeOff();
11353         break;
11354       case MachinePlaysWhite:
11355       case MachinePlaysBlack:
11356       case BeginningOfGame:
11357         SendToProgram("force\n", &first);
11358         SetUserThinkingEnables();
11359         break;
11360       case PlayFromGameFile:
11361         (void) StopLoadGameTimer();
11362         if (gameFileFP != NULL) {
11363             gameFileFP = NULL;
11364         }
11365         break;
11366       case EditPosition:
11367         EditPositionDone(TRUE);
11368         break;
11369       case AnalyzeMode:
11370       case AnalyzeFile:
11371         ExitAnalyzeMode();
11372         SendToProgram("force\n", &first);
11373         break;
11374       case TwoMachinesPlay:
11375         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11376         ResurrectChessProgram();
11377         SetUserThinkingEnables();
11378         break;
11379       case EndOfGame:
11380         ResurrectChessProgram();
11381         break;
11382       case IcsPlayingBlack:
11383       case IcsPlayingWhite:
11384         DisplayError(_("Warning: You are still playing a game"), 0);
11385         break;
11386       case IcsObserving:
11387         DisplayError(_("Warning: You are still observing a game"), 0);
11388         break;
11389       case IcsExamining:
11390         DisplayError(_("Warning: You are still examining a game"), 0);
11391         break;
11392       case IcsIdle:
11393         break;
11394       case EditGame:
11395       default:
11396         return;
11397     }
11398     
11399     pausing = FALSE;
11400     StopClocks();
11401     first.offeredDraw = second.offeredDraw = 0;
11402
11403     if (gameMode == PlayFromGameFile) {
11404         whiteTimeRemaining = timeRemaining[0][currentMove];
11405         blackTimeRemaining = timeRemaining[1][currentMove];
11406         DisplayTitle("");
11407     }
11408
11409     if (gameMode == MachinePlaysWhite ||
11410         gameMode == MachinePlaysBlack ||
11411         gameMode == TwoMachinesPlay ||
11412         gameMode == EndOfGame) {
11413         i = forwardMostMove;
11414         while (i > currentMove) {
11415             SendToProgram("undo\n", &first);
11416             i--;
11417         }
11418         whiteTimeRemaining = timeRemaining[0][currentMove];
11419         blackTimeRemaining = timeRemaining[1][currentMove];
11420         DisplayBothClocks();
11421         if (whiteFlag || blackFlag) {
11422             whiteFlag = blackFlag = 0;
11423         }
11424         DisplayTitle("");
11425     }           
11426     
11427     gameMode = EditGame;
11428     ModeHighlight();
11429     SetGameInfo();
11430 }
11431
11432
11433 void
11434 EditPositionEvent()
11435 {
11436     if (gameMode == EditPosition) {
11437         EditGameEvent();
11438         return;
11439     }
11440     
11441     EditGameEvent();
11442     if (gameMode != EditGame) return;
11443     
11444     gameMode = EditPosition;
11445     ModeHighlight();
11446     SetGameInfo();
11447     if (currentMove > 0)
11448       CopyBoard(boards[0], boards[currentMove]);
11449     
11450     blackPlaysFirst = !WhiteOnMove(currentMove);
11451     ResetClocks();
11452     currentMove = forwardMostMove = backwardMostMove = 0;
11453     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11454     DisplayMove(-1);
11455 }
11456
11457 void
11458 ExitAnalyzeMode()
11459 {
11460     /* [DM] icsEngineAnalyze - possible call from other functions */
11461     if (appData.icsEngineAnalyze) {
11462         appData.icsEngineAnalyze = FALSE;
11463
11464         DisplayMessage("",_("Close ICS engine analyze..."));
11465     }
11466     if (first.analysisSupport && first.analyzing) {
11467       SendToProgram("exit\n", &first);
11468       first.analyzing = FALSE;
11469     }
11470     thinkOutput[0] = NULLCHAR;
11471 }
11472
11473 void
11474 EditPositionDone(Boolean fakeRights)
11475 {
11476     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11477
11478     startedFromSetupPosition = TRUE;
11479     InitChessProgram(&first, FALSE);
11480     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11481       boards[0][EP_STATUS] = EP_NONE;
11482       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11483     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11484         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11485         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11486       } else boards[0][CASTLING][2] = NoRights;
11487     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11488         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11489         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11490       } else boards[0][CASTLING][5] = NoRights;
11491     }
11492     SendToProgram("force\n", &first);
11493     if (blackPlaysFirst) {
11494         strcpy(moveList[0], "");
11495         strcpy(parseList[0], "");
11496         currentMove = forwardMostMove = backwardMostMove = 1;
11497         CopyBoard(boards[1], boards[0]);
11498     } else {
11499         currentMove = forwardMostMove = backwardMostMove = 0;
11500     }
11501     SendBoard(&first, forwardMostMove);
11502     if (appData.debugMode) {
11503         fprintf(debugFP, "EditPosDone\n");
11504     }
11505     DisplayTitle("");
11506     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11507     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11508     gameMode = EditGame;
11509     ModeHighlight();
11510     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11511     ClearHighlights(); /* [AS] */
11512 }
11513
11514 /* Pause for `ms' milliseconds */
11515 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11516 void
11517 TimeDelay(ms)
11518      long ms;
11519 {
11520     TimeMark m1, m2;
11521
11522     GetTimeMark(&m1);
11523     do {
11524         GetTimeMark(&m2);
11525     } while (SubtractTimeMarks(&m2, &m1) < ms);
11526 }
11527
11528 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11529 void
11530 SendMultiLineToICS(buf)
11531      char *buf;
11532 {
11533     char temp[MSG_SIZ+1], *p;
11534     int len;
11535
11536     len = strlen(buf);
11537     if (len > MSG_SIZ)
11538       len = MSG_SIZ;
11539   
11540     strncpy(temp, buf, len);
11541     temp[len] = 0;
11542
11543     p = temp;
11544     while (*p) {
11545         if (*p == '\n' || *p == '\r')
11546           *p = ' ';
11547         ++p;
11548     }
11549
11550     strcat(temp, "\n");
11551     SendToICS(temp);
11552     SendToPlayer(temp, strlen(temp));
11553 }
11554
11555 void
11556 SetWhiteToPlayEvent()
11557 {
11558     if (gameMode == EditPosition) {
11559         blackPlaysFirst = FALSE;
11560         DisplayBothClocks();    /* works because currentMove is 0 */
11561     } else if (gameMode == IcsExamining) {
11562         SendToICS(ics_prefix);
11563         SendToICS("tomove white\n");
11564     }
11565 }
11566
11567 void
11568 SetBlackToPlayEvent()
11569 {
11570     if (gameMode == EditPosition) {
11571         blackPlaysFirst = TRUE;
11572         currentMove = 1;        /* kludge */
11573         DisplayBothClocks();
11574         currentMove = 0;
11575     } else if (gameMode == IcsExamining) {
11576         SendToICS(ics_prefix);
11577         SendToICS("tomove black\n");
11578     }
11579 }
11580
11581 void
11582 EditPositionMenuEvent(selection, x, y)
11583      ChessSquare selection;
11584      int x, y;
11585 {
11586     char buf[MSG_SIZ];
11587     ChessSquare piece = boards[0][y][x];
11588
11589     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11590
11591     switch (selection) {
11592       case ClearBoard:
11593         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11594             SendToICS(ics_prefix);
11595             SendToICS("bsetup clear\n");
11596         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11597             SendToICS(ics_prefix);
11598             SendToICS("clearboard\n");
11599         } else {
11600             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11601                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11602                 for (y = 0; y < BOARD_HEIGHT; y++) {
11603                     if (gameMode == IcsExamining) {
11604                         if (boards[currentMove][y][x] != EmptySquare) {
11605                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11606                                     AAA + x, ONE + y);
11607                             SendToICS(buf);
11608                         }
11609                     } else {
11610                         boards[0][y][x] = p;
11611                     }
11612                 }
11613             }
11614         }
11615         if (gameMode == EditPosition) {
11616             DrawPosition(FALSE, boards[0]);
11617         }
11618         break;
11619
11620       case WhitePlay:
11621         SetWhiteToPlayEvent();
11622         break;
11623
11624       case BlackPlay:
11625         SetBlackToPlayEvent();
11626         break;
11627
11628       case EmptySquare:
11629         if (gameMode == IcsExamining) {
11630             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11631             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11632             SendToICS(buf);
11633         } else {
11634             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11635                 if(x == BOARD_LEFT-2) {
11636                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11637                     boards[0][y][1] = 0;
11638                 } else
11639                 if(x == BOARD_RGHT+1) {
11640                     if(y >= gameInfo.holdingsSize) break;
11641                     boards[0][y][BOARD_WIDTH-2] = 0;
11642                 } else break;
11643             }
11644             boards[0][y][x] = EmptySquare;
11645             DrawPosition(FALSE, boards[0]);
11646         }
11647         break;
11648
11649       case PromotePiece:
11650         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11651            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11652             selection = (ChessSquare) (PROMOTED piece);
11653         } else if(piece == EmptySquare) selection = WhiteSilver;
11654         else selection = (ChessSquare)((int)piece - 1);
11655         goto defaultlabel;
11656
11657       case DemotePiece:
11658         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11659            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11660             selection = (ChessSquare) (DEMOTED piece);
11661         } else if(piece == EmptySquare) selection = BlackSilver;
11662         else selection = (ChessSquare)((int)piece + 1);       
11663         goto defaultlabel;
11664
11665       case WhiteQueen:
11666       case BlackQueen:
11667         if(gameInfo.variant == VariantShatranj ||
11668            gameInfo.variant == VariantXiangqi  ||
11669            gameInfo.variant == VariantCourier  ||
11670            gameInfo.variant == VariantMakruk     )
11671             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11672         goto defaultlabel;
11673
11674       case WhiteKing:
11675       case BlackKing:
11676         if(gameInfo.variant == VariantXiangqi)
11677             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11678         if(gameInfo.variant == VariantKnightmate)
11679             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11680       default:
11681         defaultlabel:
11682         if (gameMode == IcsExamining) {
11683             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11684             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11685                     PieceToChar(selection), AAA + x, ONE + y);
11686             SendToICS(buf);
11687         } else {
11688             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11689                 int n;
11690                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11691                     n = PieceToNumber(selection - BlackPawn);
11692                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11693                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11694                     boards[0][BOARD_HEIGHT-1-n][1]++;
11695                 } else
11696                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11697                     n = PieceToNumber(selection);
11698                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11699                     boards[0][n][BOARD_WIDTH-1] = selection;
11700                     boards[0][n][BOARD_WIDTH-2]++;
11701                 }
11702             } else
11703             boards[0][y][x] = selection;
11704             DrawPosition(TRUE, boards[0]);
11705         }
11706         break;
11707     }
11708 }
11709
11710
11711 void
11712 DropMenuEvent(selection, x, y)
11713      ChessSquare selection;
11714      int x, y;
11715 {
11716     ChessMove moveType;
11717
11718     switch (gameMode) {
11719       case IcsPlayingWhite:
11720       case MachinePlaysBlack:
11721         if (!WhiteOnMove(currentMove)) {
11722             DisplayMoveError(_("It is Black's turn"));
11723             return;
11724         }
11725         moveType = WhiteDrop;
11726         break;
11727       case IcsPlayingBlack:
11728       case MachinePlaysWhite:
11729         if (WhiteOnMove(currentMove)) {
11730             DisplayMoveError(_("It is White's turn"));
11731             return;
11732         }
11733         moveType = BlackDrop;
11734         break;
11735       case EditGame:
11736         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11737         break;
11738       default:
11739         return;
11740     }
11741
11742     if (moveType == BlackDrop && selection < BlackPawn) {
11743       selection = (ChessSquare) ((int) selection
11744                                  + (int) BlackPawn - (int) WhitePawn);
11745     }
11746     if (boards[currentMove][y][x] != EmptySquare) {
11747         DisplayMoveError(_("That square is occupied"));
11748         return;
11749     }
11750
11751     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11752 }
11753
11754 void
11755 AcceptEvent()
11756 {
11757     /* Accept a pending offer of any kind from opponent */
11758     
11759     if (appData.icsActive) {
11760         SendToICS(ics_prefix);
11761         SendToICS("accept\n");
11762     } else if (cmailMsgLoaded) {
11763         if (currentMove == cmailOldMove &&
11764             commentList[cmailOldMove] != NULL &&
11765             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11766                    "Black offers a draw" : "White offers a draw")) {
11767             TruncateGame();
11768             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11769             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11770         } else {
11771             DisplayError(_("There is no pending offer on this move"), 0);
11772             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11773         }
11774     } else {
11775         /* Not used for offers from chess program */
11776     }
11777 }
11778
11779 void
11780 DeclineEvent()
11781 {
11782     /* Decline a pending offer of any kind from opponent */
11783     
11784     if (appData.icsActive) {
11785         SendToICS(ics_prefix);
11786         SendToICS("decline\n");
11787     } else if (cmailMsgLoaded) {
11788         if (currentMove == cmailOldMove &&
11789             commentList[cmailOldMove] != NULL &&
11790             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11791                    "Black offers a draw" : "White offers a draw")) {
11792 #ifdef NOTDEF
11793             AppendComment(cmailOldMove, "Draw declined", TRUE);
11794             DisplayComment(cmailOldMove - 1, "Draw declined");
11795 #endif /*NOTDEF*/
11796         } else {
11797             DisplayError(_("There is no pending offer on this move"), 0);
11798         }
11799     } else {
11800         /* Not used for offers from chess program */
11801     }
11802 }
11803
11804 void
11805 RematchEvent()
11806 {
11807     /* Issue ICS rematch command */
11808     if (appData.icsActive) {
11809         SendToICS(ics_prefix);
11810         SendToICS("rematch\n");
11811     }
11812 }
11813
11814 void
11815 CallFlagEvent()
11816 {
11817     /* Call your opponent's flag (claim a win on time) */
11818     if (appData.icsActive) {
11819         SendToICS(ics_prefix);
11820         SendToICS("flag\n");
11821     } else {
11822         switch (gameMode) {
11823           default:
11824             return;
11825           case MachinePlaysWhite:
11826             if (whiteFlag) {
11827                 if (blackFlag)
11828                   GameEnds(GameIsDrawn, "Both players ran out of time",
11829                            GE_PLAYER);
11830                 else
11831                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11832             } else {
11833                 DisplayError(_("Your opponent is not out of time"), 0);
11834             }
11835             break;
11836           case MachinePlaysBlack:
11837             if (blackFlag) {
11838                 if (whiteFlag)
11839                   GameEnds(GameIsDrawn, "Both players ran out of time",
11840                            GE_PLAYER);
11841                 else
11842                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11843             } else {
11844                 DisplayError(_("Your opponent is not out of time"), 0);
11845             }
11846             break;
11847         }
11848     }
11849 }
11850
11851 void
11852 DrawEvent()
11853 {
11854     /* Offer draw or accept pending draw offer from opponent */
11855     
11856     if (appData.icsActive) {
11857         /* Note: tournament rules require draw offers to be
11858            made after you make your move but before you punch
11859            your clock.  Currently ICS doesn't let you do that;
11860            instead, you immediately punch your clock after making
11861            a move, but you can offer a draw at any time. */
11862         
11863         SendToICS(ics_prefix);
11864         SendToICS("draw\n");
11865         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
11866     } else if (cmailMsgLoaded) {
11867         if (currentMove == cmailOldMove &&
11868             commentList[cmailOldMove] != NULL &&
11869             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11870                    "Black offers a draw" : "White offers a draw")) {
11871             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11872             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11873         } else if (currentMove == cmailOldMove + 1) {
11874             char *offer = WhiteOnMove(cmailOldMove) ?
11875               "White offers a draw" : "Black offers a draw";
11876             AppendComment(currentMove, offer, TRUE);
11877             DisplayComment(currentMove - 1, offer);
11878             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11879         } else {
11880             DisplayError(_("You must make your move before offering a draw"), 0);
11881             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11882         }
11883     } else if (first.offeredDraw) {
11884         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11885     } else {
11886         if (first.sendDrawOffers) {
11887             SendToProgram("draw\n", &first);
11888             userOfferedDraw = TRUE;
11889         }
11890     }
11891 }
11892
11893 void
11894 AdjournEvent()
11895 {
11896     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11897     
11898     if (appData.icsActive) {
11899         SendToICS(ics_prefix);
11900         SendToICS("adjourn\n");
11901     } else {
11902         /* Currently GNU Chess doesn't offer or accept Adjourns */
11903     }
11904 }
11905
11906
11907 void
11908 AbortEvent()
11909 {
11910     /* Offer Abort or accept pending Abort offer from opponent */
11911     
11912     if (appData.icsActive) {
11913         SendToICS(ics_prefix);
11914         SendToICS("abort\n");
11915     } else {
11916         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11917     }
11918 }
11919
11920 void
11921 ResignEvent()
11922 {
11923     /* Resign.  You can do this even if it's not your turn. */
11924     
11925     if (appData.icsActive) {
11926         SendToICS(ics_prefix);
11927         SendToICS("resign\n");
11928     } else {
11929         switch (gameMode) {
11930           case MachinePlaysWhite:
11931             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11932             break;
11933           case MachinePlaysBlack:
11934             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11935             break;
11936           case EditGame:
11937             if (cmailMsgLoaded) {
11938                 TruncateGame();
11939                 if (WhiteOnMove(cmailOldMove)) {
11940                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11941                 } else {
11942                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11943                 }
11944                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11945             }
11946             break;
11947           default:
11948             break;
11949         }
11950     }
11951 }
11952
11953
11954 void
11955 StopObservingEvent()
11956 {
11957     /* Stop observing current games */
11958     SendToICS(ics_prefix);
11959     SendToICS("unobserve\n");
11960 }
11961
11962 void
11963 StopExaminingEvent()
11964 {
11965     /* Stop observing current game */
11966     SendToICS(ics_prefix);
11967     SendToICS("unexamine\n");
11968 }
11969
11970 void
11971 ForwardInner(target)
11972      int target;
11973 {
11974     int limit;
11975
11976     if (appData.debugMode)
11977         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11978                 target, currentMove, forwardMostMove);
11979
11980     if (gameMode == EditPosition)
11981       return;
11982
11983     if (gameMode == PlayFromGameFile && !pausing)
11984       PauseEvent();
11985     
11986     if (gameMode == IcsExamining && pausing)
11987       limit = pauseExamForwardMostMove;
11988     else
11989       limit = forwardMostMove;
11990     
11991     if (target > limit) target = limit;
11992
11993     if (target > 0 && moveList[target - 1][0]) {
11994         int fromX, fromY, toX, toY;
11995         toX = moveList[target - 1][2] - AAA;
11996         toY = moveList[target - 1][3] - ONE;
11997         if (moveList[target - 1][1] == '@') {
11998             if (appData.highlightLastMove) {
11999                 SetHighlights(-1, -1, toX, toY);
12000             }
12001         } else {
12002             fromX = moveList[target - 1][0] - AAA;
12003             fromY = moveList[target - 1][1] - ONE;
12004             if (target == currentMove + 1) {
12005                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12006             }
12007             if (appData.highlightLastMove) {
12008                 SetHighlights(fromX, fromY, toX, toY);
12009             }
12010         }
12011     }
12012     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12013         gameMode == Training || gameMode == PlayFromGameFile || 
12014         gameMode == AnalyzeFile) {
12015         while (currentMove < target) {
12016             SendMoveToProgram(currentMove++, &first);
12017         }
12018     } else {
12019         currentMove = target;
12020     }
12021     
12022     if (gameMode == EditGame || gameMode == EndOfGame) {
12023         whiteTimeRemaining = timeRemaining[0][currentMove];
12024         blackTimeRemaining = timeRemaining[1][currentMove];
12025     }
12026     DisplayBothClocks();
12027     DisplayMove(currentMove - 1);
12028     DrawPosition(FALSE, boards[currentMove]);
12029     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12030     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12031         DisplayComment(currentMove - 1, commentList[currentMove]);
12032     }
12033 }
12034
12035
12036 void
12037 ForwardEvent()
12038 {
12039     if (gameMode == IcsExamining && !pausing) {
12040         SendToICS(ics_prefix);
12041         SendToICS("forward\n");
12042     } else {
12043         ForwardInner(currentMove + 1);
12044     }
12045 }
12046
12047 void
12048 ToEndEvent()
12049 {
12050     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12051         /* to optimze, we temporarily turn off analysis mode while we feed
12052          * the remaining moves to the engine. Otherwise we get analysis output
12053          * after each move.
12054          */ 
12055         if (first.analysisSupport) {
12056           SendToProgram("exit\nforce\n", &first);
12057           first.analyzing = FALSE;
12058         }
12059     }
12060         
12061     if (gameMode == IcsExamining && !pausing) {
12062         SendToICS(ics_prefix);
12063         SendToICS("forward 999999\n");
12064     } else {
12065         ForwardInner(forwardMostMove);
12066     }
12067
12068     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12069         /* we have fed all the moves, so reactivate analysis mode */
12070         SendToProgram("analyze\n", &first);
12071         first.analyzing = TRUE;
12072         /*first.maybeThinking = TRUE;*/
12073         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12074     }
12075 }
12076
12077 void
12078 BackwardInner(target)
12079      int target;
12080 {
12081     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12082
12083     if (appData.debugMode)
12084         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12085                 target, currentMove, forwardMostMove);
12086
12087     if (gameMode == EditPosition) return;
12088     if (currentMove <= backwardMostMove) {
12089         ClearHighlights();
12090         DrawPosition(full_redraw, boards[currentMove]);
12091         return;
12092     }
12093     if (gameMode == PlayFromGameFile && !pausing)
12094       PauseEvent();
12095     
12096     if (moveList[target][0]) {
12097         int fromX, fromY, toX, toY;
12098         toX = moveList[target][2] - AAA;
12099         toY = moveList[target][3] - ONE;
12100         if (moveList[target][1] == '@') {
12101             if (appData.highlightLastMove) {
12102                 SetHighlights(-1, -1, toX, toY);
12103             }
12104         } else {
12105             fromX = moveList[target][0] - AAA;
12106             fromY = moveList[target][1] - ONE;
12107             if (target == currentMove - 1) {
12108                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12109             }
12110             if (appData.highlightLastMove) {
12111                 SetHighlights(fromX, fromY, toX, toY);
12112             }
12113         }
12114     }
12115     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12116         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12117         while (currentMove > target) {
12118             SendToProgram("undo\n", &first);
12119             currentMove--;
12120         }
12121     } else {
12122         currentMove = target;
12123     }
12124     
12125     if (gameMode == EditGame || gameMode == EndOfGame) {
12126         whiteTimeRemaining = timeRemaining[0][currentMove];
12127         blackTimeRemaining = timeRemaining[1][currentMove];
12128     }
12129     DisplayBothClocks();
12130     DisplayMove(currentMove - 1);
12131     DrawPosition(full_redraw, boards[currentMove]);
12132     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12133     // [HGM] PV info: routine tests if comment empty
12134     DisplayComment(currentMove - 1, commentList[currentMove]);
12135 }
12136
12137 void
12138 BackwardEvent()
12139 {
12140     if (gameMode == IcsExamining && !pausing) {
12141         SendToICS(ics_prefix);
12142         SendToICS("backward\n");
12143     } else {
12144         BackwardInner(currentMove - 1);
12145     }
12146 }
12147
12148 void
12149 ToStartEvent()
12150 {
12151     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12152         /* to optimize, we temporarily turn off analysis mode while we undo
12153          * all the moves. Otherwise we get analysis output after each undo.
12154          */ 
12155         if (first.analysisSupport) {
12156           SendToProgram("exit\nforce\n", &first);
12157           first.analyzing = FALSE;
12158         }
12159     }
12160
12161     if (gameMode == IcsExamining && !pausing) {
12162         SendToICS(ics_prefix);
12163         SendToICS("backward 999999\n");
12164     } else {
12165         BackwardInner(backwardMostMove);
12166     }
12167
12168     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12169         /* we have fed all the moves, so reactivate analysis mode */
12170         SendToProgram("analyze\n", &first);
12171         first.analyzing = TRUE;
12172         /*first.maybeThinking = TRUE;*/
12173         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12174     }
12175 }
12176
12177 void
12178 ToNrEvent(int to)
12179 {
12180   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12181   if (to >= forwardMostMove) to = forwardMostMove;
12182   if (to <= backwardMostMove) to = backwardMostMove;
12183   if (to < currentMove) {
12184     BackwardInner(to);
12185   } else {
12186     ForwardInner(to);
12187   }
12188 }
12189
12190 void
12191 RevertEvent()
12192 {
12193     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12194         return;
12195     }
12196     if (gameMode != IcsExamining) {
12197         DisplayError(_("You are not examining a game"), 0);
12198         return;
12199     }
12200     if (pausing) {
12201         DisplayError(_("You can't revert while pausing"), 0);
12202         return;
12203     }
12204     SendToICS(ics_prefix);
12205     SendToICS("revert\n");
12206 }
12207
12208 void
12209 RetractMoveEvent()
12210 {
12211     switch (gameMode) {
12212       case MachinePlaysWhite:
12213       case MachinePlaysBlack:
12214         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12215             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12216             return;
12217         }
12218         if (forwardMostMove < 2) return;
12219         currentMove = forwardMostMove = forwardMostMove - 2;
12220         whiteTimeRemaining = timeRemaining[0][currentMove];
12221         blackTimeRemaining = timeRemaining[1][currentMove];
12222         DisplayBothClocks();
12223         DisplayMove(currentMove - 1);
12224         ClearHighlights();/*!! could figure this out*/
12225         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12226         SendToProgram("remove\n", &first);
12227         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12228         break;
12229
12230       case BeginningOfGame:
12231       default:
12232         break;
12233
12234       case IcsPlayingWhite:
12235       case IcsPlayingBlack:
12236         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12237             SendToICS(ics_prefix);
12238             SendToICS("takeback 2\n");
12239         } else {
12240             SendToICS(ics_prefix);
12241             SendToICS("takeback 1\n");
12242         }
12243         break;
12244     }
12245 }
12246
12247 void
12248 MoveNowEvent()
12249 {
12250     ChessProgramState *cps;
12251
12252     switch (gameMode) {
12253       case MachinePlaysWhite:
12254         if (!WhiteOnMove(forwardMostMove)) {
12255             DisplayError(_("It is your turn"), 0);
12256             return;
12257         }
12258         cps = &first;
12259         break;
12260       case MachinePlaysBlack:
12261         if (WhiteOnMove(forwardMostMove)) {
12262             DisplayError(_("It is your turn"), 0);
12263             return;
12264         }
12265         cps = &first;
12266         break;
12267       case TwoMachinesPlay:
12268         if (WhiteOnMove(forwardMostMove) ==
12269             (first.twoMachinesColor[0] == 'w')) {
12270             cps = &first;
12271         } else {
12272             cps = &second;
12273         }
12274         break;
12275       case BeginningOfGame:
12276       default:
12277         return;
12278     }
12279     SendToProgram("?\n", cps);
12280 }
12281
12282 void
12283 TruncateGameEvent()
12284 {
12285     EditGameEvent();
12286     if (gameMode != EditGame) return;
12287     TruncateGame();
12288 }
12289
12290 void
12291 TruncateGame()
12292 {
12293     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12294     if (forwardMostMove > currentMove) {
12295         if (gameInfo.resultDetails != NULL) {
12296             free(gameInfo.resultDetails);
12297             gameInfo.resultDetails = NULL;
12298             gameInfo.result = GameUnfinished;
12299         }
12300         forwardMostMove = currentMove;
12301         HistorySet(parseList, backwardMostMove, forwardMostMove,
12302                    currentMove-1);
12303     }
12304 }
12305
12306 void
12307 HintEvent()
12308 {
12309     if (appData.noChessProgram) return;
12310     switch (gameMode) {
12311       case MachinePlaysWhite:
12312         if (WhiteOnMove(forwardMostMove)) {
12313             DisplayError(_("Wait until your turn"), 0);
12314             return;
12315         }
12316         break;
12317       case BeginningOfGame:
12318       case MachinePlaysBlack:
12319         if (!WhiteOnMove(forwardMostMove)) {
12320             DisplayError(_("Wait until your turn"), 0);
12321             return;
12322         }
12323         break;
12324       default:
12325         DisplayError(_("No hint available"), 0);
12326         return;
12327     }
12328     SendToProgram("hint\n", &first);
12329     hintRequested = TRUE;
12330 }
12331
12332 void
12333 BookEvent()
12334 {
12335     if (appData.noChessProgram) return;
12336     switch (gameMode) {
12337       case MachinePlaysWhite:
12338         if (WhiteOnMove(forwardMostMove)) {
12339             DisplayError(_("Wait until your turn"), 0);
12340             return;
12341         }
12342         break;
12343       case BeginningOfGame:
12344       case MachinePlaysBlack:
12345         if (!WhiteOnMove(forwardMostMove)) {
12346             DisplayError(_("Wait until your turn"), 0);
12347             return;
12348         }
12349         break;
12350       case EditPosition:
12351         EditPositionDone(TRUE);
12352         break;
12353       case TwoMachinesPlay:
12354         return;
12355       default:
12356         break;
12357     }
12358     SendToProgram("bk\n", &first);
12359     bookOutput[0] = NULLCHAR;
12360     bookRequested = TRUE;
12361 }
12362
12363 void
12364 AboutGameEvent()
12365 {
12366     char *tags = PGNTags(&gameInfo);
12367     TagsPopUp(tags, CmailMsg());
12368     free(tags);
12369 }
12370
12371 /* end button procedures */
12372
12373 void
12374 PrintPosition(fp, move)
12375      FILE *fp;
12376      int move;
12377 {
12378     int i, j;
12379     
12380     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12381         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12382             char c = PieceToChar(boards[move][i][j]);
12383             fputc(c == 'x' ? '.' : c, fp);
12384             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12385         }
12386     }
12387     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12388       fprintf(fp, "white to play\n");
12389     else
12390       fprintf(fp, "black to play\n");
12391 }
12392
12393 void
12394 PrintOpponents(fp)
12395      FILE *fp;
12396 {
12397     if (gameInfo.white != NULL) {
12398         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12399     } else {
12400         fprintf(fp, "\n");
12401     }
12402 }
12403
12404 /* Find last component of program's own name, using some heuristics */
12405 void
12406 TidyProgramName(prog, host, buf)
12407      char *prog, *host, buf[MSG_SIZ];
12408 {
12409     char *p, *q;
12410     int local = (strcmp(host, "localhost") == 0);
12411     while (!local && (p = strchr(prog, ';')) != NULL) {
12412         p++;
12413         while (*p == ' ') p++;
12414         prog = p;
12415     }
12416     if (*prog == '"' || *prog == '\'') {
12417         q = strchr(prog + 1, *prog);
12418     } else {
12419         q = strchr(prog, ' ');
12420     }
12421     if (q == NULL) q = prog + strlen(prog);
12422     p = q;
12423     while (p >= prog && *p != '/' && *p != '\\') p--;
12424     p++;
12425     if(p == prog && *p == '"') p++;
12426     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12427     memcpy(buf, p, q - p);
12428     buf[q - p] = NULLCHAR;
12429     if (!local) {
12430         strcat(buf, "@");
12431         strcat(buf, host);
12432     }
12433 }
12434
12435 char *
12436 TimeControlTagValue()
12437 {
12438     char buf[MSG_SIZ];
12439     if (!appData.clockMode) {
12440         strcpy(buf, "-");
12441     } else if (movesPerSession > 0) {
12442         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12443     } else if (timeIncrement == 0) {
12444         sprintf(buf, "%ld", timeControl/1000);
12445     } else {
12446         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12447     }
12448     return StrSave(buf);
12449 }
12450
12451 void
12452 SetGameInfo()
12453 {
12454     /* This routine is used only for certain modes */
12455     VariantClass v = gameInfo.variant;
12456     ChessMove r = GameUnfinished;
12457     char *p = NULL;
12458
12459     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12460         r = gameInfo.result; 
12461         p = gameInfo.resultDetails; 
12462         gameInfo.resultDetails = NULL;
12463     }
12464     ClearGameInfo(&gameInfo);
12465     gameInfo.variant = v;
12466
12467     switch (gameMode) {
12468       case MachinePlaysWhite:
12469         gameInfo.event = StrSave( appData.pgnEventHeader );
12470         gameInfo.site = StrSave(HostName());
12471         gameInfo.date = PGNDate();
12472         gameInfo.round = StrSave("-");
12473         gameInfo.white = StrSave(first.tidy);
12474         gameInfo.black = StrSave(UserName());
12475         gameInfo.timeControl = TimeControlTagValue();
12476         break;
12477
12478       case MachinePlaysBlack:
12479         gameInfo.event = StrSave( appData.pgnEventHeader );
12480         gameInfo.site = StrSave(HostName());
12481         gameInfo.date = PGNDate();
12482         gameInfo.round = StrSave("-");
12483         gameInfo.white = StrSave(UserName());
12484         gameInfo.black = StrSave(first.tidy);
12485         gameInfo.timeControl = TimeControlTagValue();
12486         break;
12487
12488       case TwoMachinesPlay:
12489         gameInfo.event = StrSave( appData.pgnEventHeader );
12490         gameInfo.site = StrSave(HostName());
12491         gameInfo.date = PGNDate();
12492         if (matchGame > 0) {
12493             char buf[MSG_SIZ];
12494             sprintf(buf, "%d", matchGame);
12495             gameInfo.round = StrSave(buf);
12496         } else {
12497             gameInfo.round = StrSave("-");
12498         }
12499         if (first.twoMachinesColor[0] == 'w') {
12500             gameInfo.white = StrSave(first.tidy);
12501             gameInfo.black = StrSave(second.tidy);
12502         } else {
12503             gameInfo.white = StrSave(second.tidy);
12504             gameInfo.black = StrSave(first.tidy);
12505         }
12506         gameInfo.timeControl = TimeControlTagValue();
12507         break;
12508
12509       case EditGame:
12510         gameInfo.event = StrSave("Edited game");
12511         gameInfo.site = StrSave(HostName());
12512         gameInfo.date = PGNDate();
12513         gameInfo.round = StrSave("-");
12514         gameInfo.white = StrSave("-");
12515         gameInfo.black = StrSave("-");
12516         gameInfo.result = r;
12517         gameInfo.resultDetails = p;
12518         break;
12519
12520       case EditPosition:
12521         gameInfo.event = StrSave("Edited position");
12522         gameInfo.site = StrSave(HostName());
12523         gameInfo.date = PGNDate();
12524         gameInfo.round = StrSave("-");
12525         gameInfo.white = StrSave("-");
12526         gameInfo.black = StrSave("-");
12527         break;
12528
12529       case IcsPlayingWhite:
12530       case IcsPlayingBlack:
12531       case IcsObserving:
12532       case IcsExamining:
12533         break;
12534
12535       case PlayFromGameFile:
12536         gameInfo.event = StrSave("Game from non-PGN file");
12537         gameInfo.site = StrSave(HostName());
12538         gameInfo.date = PGNDate();
12539         gameInfo.round = StrSave("-");
12540         gameInfo.white = StrSave("?");
12541         gameInfo.black = StrSave("?");
12542         break;
12543
12544       default:
12545         break;
12546     }
12547 }
12548
12549 void
12550 ReplaceComment(index, text)
12551      int index;
12552      char *text;
12553 {
12554     int len;
12555
12556     while (*text == '\n') text++;
12557     len = strlen(text);
12558     while (len > 0 && text[len - 1] == '\n') len--;
12559
12560     if (commentList[index] != NULL)
12561       free(commentList[index]);
12562
12563     if (len == 0) {
12564         commentList[index] = NULL;
12565         return;
12566     }
12567   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12568       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12569       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12570     commentList[index] = (char *) malloc(len + 2);
12571     strncpy(commentList[index], text, len);
12572     commentList[index][len] = '\n';
12573     commentList[index][len + 1] = NULLCHAR;
12574   } else { 
12575     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12576     char *p;
12577     commentList[index] = (char *) malloc(len + 6);
12578     strcpy(commentList[index], "{\n");
12579     strncpy(commentList[index]+2, text, len);
12580     commentList[index][len+2] = NULLCHAR;
12581     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12582     strcat(commentList[index], "\n}\n");
12583   }
12584 }
12585
12586 void
12587 CrushCRs(text)
12588      char *text;
12589 {
12590   char *p = text;
12591   char *q = text;
12592   char ch;
12593
12594   do {
12595     ch = *p++;
12596     if (ch == '\r') continue;
12597     *q++ = ch;
12598   } while (ch != '\0');
12599 }
12600
12601 void
12602 AppendComment(index, text, addBraces)
12603      int index;
12604      char *text;
12605      Boolean addBraces; // [HGM] braces: tells if we should add {}
12606 {
12607     int oldlen, len;
12608     char *old;
12609
12610 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12611     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12612
12613     CrushCRs(text);
12614     while (*text == '\n') text++;
12615     len = strlen(text);
12616     while (len > 0 && text[len - 1] == '\n') len--;
12617
12618     if (len == 0) return;
12619
12620     if (commentList[index] != NULL) {
12621         old = commentList[index];
12622         oldlen = strlen(old);
12623         while(commentList[index][oldlen-1] ==  '\n')
12624           commentList[index][--oldlen] = NULLCHAR;
12625         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12626         strcpy(commentList[index], old);
12627         free(old);
12628         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12629         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12630           if(addBraces) addBraces = FALSE; else { text++; len--; }
12631           while (*text == '\n') { text++; len--; }
12632           commentList[index][--oldlen] = NULLCHAR;
12633       }
12634         if(addBraces) strcat(commentList[index], "\n{\n");
12635         else          strcat(commentList[index], "\n");
12636         strcat(commentList[index], text);
12637         if(addBraces) strcat(commentList[index], "\n}\n");
12638         else          strcat(commentList[index], "\n");
12639     } else {
12640         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12641         if(addBraces)
12642              strcpy(commentList[index], "{\n");
12643         else commentList[index][0] = NULLCHAR;
12644         strcat(commentList[index], text);
12645         strcat(commentList[index], "\n");
12646         if(addBraces) strcat(commentList[index], "}\n");
12647     }
12648 }
12649
12650 static char * FindStr( char * text, char * sub_text )
12651 {
12652     char * result = strstr( text, sub_text );
12653
12654     if( result != NULL ) {
12655         result += strlen( sub_text );
12656     }
12657
12658     return result;
12659 }
12660
12661 /* [AS] Try to extract PV info from PGN comment */
12662 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12663 char *GetInfoFromComment( int index, char * text )
12664 {
12665     char * sep = text;
12666
12667     if( text != NULL && index > 0 ) {
12668         int score = 0;
12669         int depth = 0;
12670         int time = -1, sec = 0, deci;
12671         char * s_eval = FindStr( text, "[%eval " );
12672         char * s_emt = FindStr( text, "[%emt " );
12673
12674         if( s_eval != NULL || s_emt != NULL ) {
12675             /* New style */
12676             char delim;
12677
12678             if( s_eval != NULL ) {
12679                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12680                     return text;
12681                 }
12682
12683                 if( delim != ']' ) {
12684                     return text;
12685                 }
12686             }
12687
12688             if( s_emt != NULL ) {
12689             }
12690                 return text;
12691         }
12692         else {
12693             /* We expect something like: [+|-]nnn.nn/dd */
12694             int score_lo = 0;
12695
12696             if(*text != '{') return text; // [HGM] braces: must be normal comment
12697
12698             sep = strchr( text, '/' );
12699             if( sep == NULL || sep < (text+4) ) {
12700                 return text;
12701             }
12702
12703             time = -1; sec = -1; deci = -1;
12704             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12705                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12706                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12707                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12708                 return text;
12709             }
12710
12711             if( score_lo < 0 || score_lo >= 100 ) {
12712                 return text;
12713             }
12714
12715             if(sec >= 0) time = 600*time + 10*sec; else
12716             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12717
12718             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12719
12720             /* [HGM] PV time: now locate end of PV info */
12721             while( *++sep >= '0' && *sep <= '9'); // strip depth
12722             if(time >= 0)
12723             while( *++sep >= '0' && *sep <= '9'); // strip time
12724             if(sec >= 0)
12725             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12726             if(deci >= 0)
12727             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12728             while(*sep == ' ') sep++;
12729         }
12730
12731         if( depth <= 0 ) {
12732             return text;
12733         }
12734
12735         if( time < 0 ) {
12736             time = -1;
12737         }
12738
12739         pvInfoList[index-1].depth = depth;
12740         pvInfoList[index-1].score = score;
12741         pvInfoList[index-1].time  = 10*time; // centi-sec
12742         if(*sep == '}') *sep = 0; else *--sep = '{';
12743     }
12744     return sep;
12745 }
12746
12747 void
12748 SendToProgram(message, cps)
12749      char *message;
12750      ChessProgramState *cps;
12751 {
12752     int count, outCount, error;
12753     char buf[MSG_SIZ];
12754
12755     if (cps->pr == NULL) return;
12756     Attention(cps);
12757     
12758     if (appData.debugMode) {
12759         TimeMark now;
12760         GetTimeMark(&now);
12761         fprintf(debugFP, "%ld >%-6s: %s", 
12762                 SubtractTimeMarks(&now, &programStartTime),
12763                 cps->which, message);
12764     }
12765     
12766     count = strlen(message);
12767     outCount = OutputToProcess(cps->pr, message, count, &error);
12768     if (outCount < count && !exiting 
12769                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12770         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12771         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12772             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12773                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12774                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12775             } else {
12776                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12777             }
12778             gameInfo.resultDetails = StrSave(buf);
12779         }
12780         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12781     }
12782 }
12783
12784 void
12785 ReceiveFromProgram(isr, closure, message, count, error)
12786      InputSourceRef isr;
12787      VOIDSTAR closure;
12788      char *message;
12789      int count;
12790      int error;
12791 {
12792     char *end_str;
12793     char buf[MSG_SIZ];
12794     ChessProgramState *cps = (ChessProgramState *)closure;
12795
12796     if (isr != cps->isr) return; /* Killed intentionally */
12797     if (count <= 0) {
12798         if (count == 0) {
12799             sprintf(buf,
12800                     _("Error: %s chess program (%s) exited unexpectedly"),
12801                     cps->which, cps->program);
12802         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12803                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12804                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12805                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12806                 } else {
12807                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12808                 }
12809                 gameInfo.resultDetails = StrSave(buf);
12810             }
12811             RemoveInputSource(cps->isr);
12812             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12813         } else {
12814             sprintf(buf,
12815                     _("Error reading from %s chess program (%s)"),
12816                     cps->which, cps->program);
12817             RemoveInputSource(cps->isr);
12818
12819             /* [AS] Program is misbehaving badly... kill it */
12820             if( count == -2 ) {
12821                 DestroyChildProcess( cps->pr, 9 );
12822                 cps->pr = NoProc;
12823             }
12824
12825             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12826         }
12827         return;
12828     }
12829     
12830     if ((end_str = strchr(message, '\r')) != NULL)
12831       *end_str = NULLCHAR;
12832     if ((end_str = strchr(message, '\n')) != NULL)
12833       *end_str = NULLCHAR;
12834     
12835     if (appData.debugMode) {
12836         TimeMark now; int print = 1;
12837         char *quote = ""; char c; int i;
12838
12839         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12840                 char start = message[0];
12841                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12842                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12843                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12844                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12845                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12846                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12847                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12848                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12849                         { quote = "# "; print = (appData.engineComments == 2); }
12850                 message[0] = start; // restore original message
12851         }
12852         if(print) {
12853                 GetTimeMark(&now);
12854                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12855                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12856                         quote,
12857                         message);
12858         }
12859     }
12860
12861     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12862     if (appData.icsEngineAnalyze) {
12863         if (strstr(message, "whisper") != NULL ||
12864              strstr(message, "kibitz") != NULL || 
12865             strstr(message, "tellics") != NULL) return;
12866     }
12867
12868     HandleMachineMove(message, cps);
12869 }
12870
12871
12872 void
12873 SendTimeControl(cps, mps, tc, inc, sd, st)
12874      ChessProgramState *cps;
12875      int mps, inc, sd, st;
12876      long tc;
12877 {
12878     char buf[MSG_SIZ];
12879     int seconds;
12880
12881     if( timeControl_2 > 0 ) {
12882         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12883             tc = timeControl_2;
12884         }
12885     }
12886     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12887     inc /= cps->timeOdds;
12888     st  /= cps->timeOdds;
12889
12890     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12891
12892     if (st > 0) {
12893       /* Set exact time per move, normally using st command */
12894       if (cps->stKludge) {
12895         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12896         seconds = st % 60;
12897         if (seconds == 0) {
12898           sprintf(buf, "level 1 %d\n", st/60);
12899         } else {
12900           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12901         }
12902       } else {
12903         sprintf(buf, "st %d\n", st);
12904       }
12905     } else {
12906       /* Set conventional or incremental time control, using level command */
12907       if (seconds == 0) {
12908         /* Note old gnuchess bug -- minutes:seconds used to not work.
12909            Fixed in later versions, but still avoid :seconds
12910            when seconds is 0. */
12911         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12912       } else {
12913         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12914                 seconds, inc/1000);
12915       }
12916     }
12917     SendToProgram(buf, cps);
12918
12919     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12920     /* Orthogonally, limit search to given depth */
12921     if (sd > 0) {
12922       if (cps->sdKludge) {
12923         sprintf(buf, "depth\n%d\n", sd);
12924       } else {
12925         sprintf(buf, "sd %d\n", sd);
12926       }
12927       SendToProgram(buf, cps);
12928     }
12929
12930     if(cps->nps > 0) { /* [HGM] nps */
12931         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12932         else {
12933                 sprintf(buf, "nps %d\n", cps->nps);
12934               SendToProgram(buf, cps);
12935         }
12936     }
12937 }
12938
12939 ChessProgramState *WhitePlayer()
12940 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12941 {
12942     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12943        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12944         return &second;
12945     return &first;
12946 }
12947
12948 void
12949 SendTimeRemaining(cps, machineWhite)
12950      ChessProgramState *cps;
12951      int /*boolean*/ machineWhite;
12952 {
12953     char message[MSG_SIZ];
12954     long time, otime;
12955
12956     /* Note: this routine must be called when the clocks are stopped
12957        or when they have *just* been set or switched; otherwise
12958        it will be off by the time since the current tick started.
12959     */
12960     if (machineWhite) {
12961         time = whiteTimeRemaining / 10;
12962         otime = blackTimeRemaining / 10;
12963     } else {
12964         time = blackTimeRemaining / 10;
12965         otime = whiteTimeRemaining / 10;
12966     }
12967     /* [HGM] translate opponent's time by time-odds factor */
12968     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12969     if (appData.debugMode) {
12970         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12971     }
12972
12973     if (time <= 0) time = 1;
12974     if (otime <= 0) otime = 1;
12975     
12976     sprintf(message, "time %ld\n", time);
12977     SendToProgram(message, cps);
12978
12979     sprintf(message, "otim %ld\n", otime);
12980     SendToProgram(message, cps);
12981 }
12982
12983 int
12984 BoolFeature(p, name, loc, cps)
12985      char **p;
12986      char *name;
12987      int *loc;
12988      ChessProgramState *cps;
12989 {
12990   char buf[MSG_SIZ];
12991   int len = strlen(name);
12992   int val;
12993   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12994     (*p) += len + 1;
12995     sscanf(*p, "%d", &val);
12996     *loc = (val != 0);
12997     while (**p && **p != ' ') (*p)++;
12998     sprintf(buf, "accepted %s\n", name);
12999     SendToProgram(buf, cps);
13000     return TRUE;
13001   }
13002   return FALSE;
13003 }
13004
13005 int
13006 IntFeature(p, name, loc, cps)
13007      char **p;
13008      char *name;
13009      int *loc;
13010      ChessProgramState *cps;
13011 {
13012   char buf[MSG_SIZ];
13013   int len = strlen(name);
13014   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13015     (*p) += len + 1;
13016     sscanf(*p, "%d", loc);
13017     while (**p && **p != ' ') (*p)++;
13018     sprintf(buf, "accepted %s\n", name);
13019     SendToProgram(buf, cps);
13020     return TRUE;
13021   }
13022   return FALSE;
13023 }
13024
13025 int
13026 StringFeature(p, name, loc, cps)
13027      char **p;
13028      char *name;
13029      char loc[];
13030      ChessProgramState *cps;
13031 {
13032   char buf[MSG_SIZ];
13033   int len = strlen(name);
13034   if (strncmp((*p), name, len) == 0
13035       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13036     (*p) += len + 2;
13037     sscanf(*p, "%[^\"]", loc);
13038     while (**p && **p != '\"') (*p)++;
13039     if (**p == '\"') (*p)++;
13040     sprintf(buf, "accepted %s\n", name);
13041     SendToProgram(buf, cps);
13042     return TRUE;
13043   }
13044   return FALSE;
13045 }
13046
13047 int 
13048 ParseOption(Option *opt, ChessProgramState *cps)
13049 // [HGM] options: process the string that defines an engine option, and determine
13050 // name, type, default value, and allowed value range
13051 {
13052         char *p, *q, buf[MSG_SIZ];
13053         int n, min = (-1)<<31, max = 1<<31, def;
13054
13055         if(p = strstr(opt->name, " -spin ")) {
13056             if((n = sscanf(p, " -spin %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;
13064         } else if((p = strstr(opt->name, " -slider "))) {
13065             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13066             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13067             if(max < min) max = min; // enforce consistency
13068             if(def < min) def = min;
13069             if(def > max) def = max;
13070             opt->value = def;
13071             opt->min = min;
13072             opt->max = max;
13073             opt->type = Spin; // Slider;
13074         } else if((p = strstr(opt->name, " -string "))) {
13075             opt->textValue = p+9;
13076             opt->type = TextBox;
13077         } else if((p = strstr(opt->name, " -file "))) {
13078             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13079             opt->textValue = p+7;
13080             opt->type = TextBox; // FileName;
13081         } else if((p = strstr(opt->name, " -path "))) {
13082             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13083             opt->textValue = p+7;
13084             opt->type = TextBox; // PathName;
13085         } else if(p = strstr(opt->name, " -check ")) {
13086             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13087             opt->value = (def != 0);
13088             opt->type = CheckBox;
13089         } else if(p = strstr(opt->name, " -combo ")) {
13090             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13091             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13092             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13093             opt->value = n = 0;
13094             while(q = StrStr(q, " /// ")) {
13095                 n++; *q = 0;    // count choices, and null-terminate each of them
13096                 q += 5;
13097                 if(*q == '*') { // remember default, which is marked with * prefix
13098                     q++;
13099                     opt->value = n;
13100                 }
13101                 cps->comboList[cps->comboCnt++] = q;
13102             }
13103             cps->comboList[cps->comboCnt++] = NULL;
13104             opt->max = n + 1;
13105             opt->type = ComboBox;
13106         } else if(p = strstr(opt->name, " -button")) {
13107             opt->type = Button;
13108         } else if(p = strstr(opt->name, " -save")) {
13109             opt->type = SaveButton;
13110         } else return FALSE;
13111         *p = 0; // terminate option name
13112         // now look if the command-line options define a setting for this engine option.
13113         if(cps->optionSettings && cps->optionSettings[0])
13114             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13115         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13116                 sprintf(buf, "option %s", p);
13117                 if(p = strstr(buf, ",")) *p = 0;
13118                 strcat(buf, "\n");
13119                 SendToProgram(buf, cps);
13120         }
13121         return TRUE;
13122 }
13123
13124 void
13125 FeatureDone(cps, val)
13126      ChessProgramState* cps;
13127      int val;
13128 {
13129   DelayedEventCallback cb = GetDelayedEvent();
13130   if ((cb == InitBackEnd3 && cps == &first) ||
13131       (cb == TwoMachinesEventIfReady && cps == &second)) {
13132     CancelDelayedEvent();
13133     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13134   }
13135   cps->initDone = val;
13136 }
13137
13138 /* Parse feature command from engine */
13139 void
13140 ParseFeatures(args, cps)
13141      char* args;
13142      ChessProgramState *cps;  
13143 {
13144   char *p = args;
13145   char *q;
13146   int val;
13147   char buf[MSG_SIZ];
13148
13149   for (;;) {
13150     while (*p == ' ') p++;
13151     if (*p == NULLCHAR) return;
13152
13153     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13154     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13155     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13156     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13157     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13158     if (BoolFeature(&p, "reuse", &val, cps)) {
13159       /* Engine can disable reuse, but can't enable it if user said no */
13160       if (!val) cps->reuse = FALSE;
13161       continue;
13162     }
13163     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13164     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13165       if (gameMode == TwoMachinesPlay) {
13166         DisplayTwoMachinesTitle();
13167       } else {
13168         DisplayTitle("");
13169       }
13170       continue;
13171     }
13172     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13173     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13174     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13175     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13176     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13177     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13178     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13179     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13180     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13181     if (IntFeature(&p, "done", &val, cps)) {
13182       FeatureDone(cps, val);
13183       continue;
13184     }
13185     /* Added by Tord: */
13186     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13187     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13188     /* End of additions by Tord */
13189
13190     /* [HGM] added features: */
13191     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13192     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13193     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13194     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13195     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13196     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13197     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13198         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13199             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13200             SendToProgram(buf, cps);
13201             continue;
13202         }
13203         if(cps->nrOptions >= MAX_OPTIONS) {
13204             cps->nrOptions--;
13205             sprintf(buf, "%s engine has too many options\n", cps->which);
13206             DisplayError(buf, 0);
13207         }
13208         continue;
13209     }
13210     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13211     /* End of additions by HGM */
13212
13213     /* unknown feature: complain and skip */
13214     q = p;
13215     while (*q && *q != '=') q++;
13216     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13217     SendToProgram(buf, cps);
13218     p = q;
13219     if (*p == '=') {
13220       p++;
13221       if (*p == '\"') {
13222         p++;
13223         while (*p && *p != '\"') p++;
13224         if (*p == '\"') p++;
13225       } else {
13226         while (*p && *p != ' ') p++;
13227       }
13228     }
13229   }
13230
13231 }
13232
13233 void
13234 PeriodicUpdatesEvent(newState)
13235      int newState;
13236 {
13237     if (newState == appData.periodicUpdates)
13238       return;
13239
13240     appData.periodicUpdates=newState;
13241
13242     /* Display type changes, so update it now */
13243 //    DisplayAnalysis();
13244
13245     /* Get the ball rolling again... */
13246     if (newState) {
13247         AnalysisPeriodicEvent(1);
13248         StartAnalysisClock();
13249     }
13250 }
13251
13252 void
13253 PonderNextMoveEvent(newState)
13254      int newState;
13255 {
13256     if (newState == appData.ponderNextMove) return;
13257     if (gameMode == EditPosition) EditPositionDone(TRUE);
13258     if (newState) {
13259         SendToProgram("hard\n", &first);
13260         if (gameMode == TwoMachinesPlay) {
13261             SendToProgram("hard\n", &second);
13262         }
13263     } else {
13264         SendToProgram("easy\n", &first);
13265         thinkOutput[0] = NULLCHAR;
13266         if (gameMode == TwoMachinesPlay) {
13267             SendToProgram("easy\n", &second);
13268         }
13269     }
13270     appData.ponderNextMove = newState;
13271 }
13272
13273 void
13274 NewSettingEvent(option, command, value)
13275      char *command;
13276      int option, value;
13277 {
13278     char buf[MSG_SIZ];
13279
13280     if (gameMode == EditPosition) EditPositionDone(TRUE);
13281     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13282     SendToProgram(buf, &first);
13283     if (gameMode == TwoMachinesPlay) {
13284         SendToProgram(buf, &second);
13285     }
13286 }
13287
13288 void
13289 ShowThinkingEvent()
13290 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13291 {
13292     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13293     int newState = appData.showThinking
13294         // [HGM] thinking: other features now need thinking output as well
13295         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13296     
13297     if (oldState == newState) return;
13298     oldState = newState;
13299     if (gameMode == EditPosition) EditPositionDone(TRUE);
13300     if (oldState) {
13301         SendToProgram("post\n", &first);
13302         if (gameMode == TwoMachinesPlay) {
13303             SendToProgram("post\n", &second);
13304         }
13305     } else {
13306         SendToProgram("nopost\n", &first);
13307         thinkOutput[0] = NULLCHAR;
13308         if (gameMode == TwoMachinesPlay) {
13309             SendToProgram("nopost\n", &second);
13310         }
13311     }
13312 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13313 }
13314
13315 void
13316 AskQuestionEvent(title, question, replyPrefix, which)
13317      char *title; char *question; char *replyPrefix; char *which;
13318 {
13319   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13320   if (pr == NoProc) return;
13321   AskQuestion(title, question, replyPrefix, pr);
13322 }
13323
13324 void
13325 DisplayMove(moveNumber)
13326      int moveNumber;
13327 {
13328     char message[MSG_SIZ];
13329     char res[MSG_SIZ];
13330     char cpThinkOutput[MSG_SIZ];
13331
13332     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13333     
13334     if (moveNumber == forwardMostMove - 1 || 
13335         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13336
13337         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13338
13339         if (strchr(cpThinkOutput, '\n')) {
13340             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13341         }
13342     } else {
13343         *cpThinkOutput = NULLCHAR;
13344     }
13345
13346     /* [AS] Hide thinking from human user */
13347     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13348         *cpThinkOutput = NULLCHAR;
13349         if( thinkOutput[0] != NULLCHAR ) {
13350             int i;
13351
13352             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13353                 cpThinkOutput[i] = '.';
13354             }
13355             cpThinkOutput[i] = NULLCHAR;
13356             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13357         }
13358     }
13359
13360     if (moveNumber == forwardMostMove - 1 &&
13361         gameInfo.resultDetails != NULL) {
13362         if (gameInfo.resultDetails[0] == NULLCHAR) {
13363             sprintf(res, " %s", PGNResult(gameInfo.result));
13364         } else {
13365             sprintf(res, " {%s} %s",
13366                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13367         }
13368     } else {
13369         res[0] = NULLCHAR;
13370     }
13371
13372     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13373         DisplayMessage(res, cpThinkOutput);
13374     } else {
13375         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13376                 WhiteOnMove(moveNumber) ? " " : ".. ",
13377                 parseList[moveNumber], res);
13378         DisplayMessage(message, cpThinkOutput);
13379     }
13380 }
13381
13382 void
13383 DisplayComment(moveNumber, text)
13384      int moveNumber;
13385      char *text;
13386 {
13387     char title[MSG_SIZ];
13388     char buf[8000]; // comment can be long!
13389     int score, depth;
13390     
13391     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13392       strcpy(title, "Comment");
13393     } else {
13394       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13395               WhiteOnMove(moveNumber) ? " " : ".. ",
13396               parseList[moveNumber]);
13397     }
13398     // [HGM] PV info: display PV info together with (or as) comment
13399     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13400       if(text == NULL) text = "";                                           
13401       score = pvInfoList[moveNumber].score;
13402       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13403               depth, (pvInfoList[moveNumber].time+50)/100, text);
13404       text = buf;
13405     }
13406     if (text != NULL && (appData.autoDisplayComment || commentUp))
13407         CommentPopUp(title, text);
13408 }
13409
13410 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13411  * might be busy thinking or pondering.  It can be omitted if your
13412  * gnuchess is configured to stop thinking immediately on any user
13413  * input.  However, that gnuchess feature depends on the FIONREAD
13414  * ioctl, which does not work properly on some flavors of Unix.
13415  */
13416 void
13417 Attention(cps)
13418      ChessProgramState *cps;
13419 {
13420 #if ATTENTION
13421     if (!cps->useSigint) return;
13422     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13423     switch (gameMode) {
13424       case MachinePlaysWhite:
13425       case MachinePlaysBlack:
13426       case TwoMachinesPlay:
13427       case IcsPlayingWhite:
13428       case IcsPlayingBlack:
13429       case AnalyzeMode:
13430       case AnalyzeFile:
13431         /* Skip if we know it isn't thinking */
13432         if (!cps->maybeThinking) return;
13433         if (appData.debugMode)
13434           fprintf(debugFP, "Interrupting %s\n", cps->which);
13435         InterruptChildProcess(cps->pr);
13436         cps->maybeThinking = FALSE;
13437         break;
13438       default:
13439         break;
13440     }
13441 #endif /*ATTENTION*/
13442 }
13443
13444 int
13445 CheckFlags()
13446 {
13447     if (whiteTimeRemaining <= 0) {
13448         if (!whiteFlag) {
13449             whiteFlag = TRUE;
13450             if (appData.icsActive) {
13451                 if (appData.autoCallFlag &&
13452                     gameMode == IcsPlayingBlack && !blackFlag) {
13453                   SendToICS(ics_prefix);
13454                   SendToICS("flag\n");
13455                 }
13456             } else {
13457                 if (blackFlag) {
13458                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13459                 } else {
13460                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13461                     if (appData.autoCallFlag) {
13462                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13463                         return TRUE;
13464                     }
13465                 }
13466             }
13467         }
13468     }
13469     if (blackTimeRemaining <= 0) {
13470         if (!blackFlag) {
13471             blackFlag = TRUE;
13472             if (appData.icsActive) {
13473                 if (appData.autoCallFlag &&
13474                     gameMode == IcsPlayingWhite && !whiteFlag) {
13475                   SendToICS(ics_prefix);
13476                   SendToICS("flag\n");
13477                 }
13478             } else {
13479                 if (whiteFlag) {
13480                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13481                 } else {
13482                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13483                     if (appData.autoCallFlag) {
13484                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13485                         return TRUE;
13486                     }
13487                 }
13488             }
13489         }
13490     }
13491     return FALSE;
13492 }
13493
13494 void
13495 CheckTimeControl()
13496 {
13497     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13498         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13499
13500     /*
13501      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13502      */
13503     if ( !WhiteOnMove(forwardMostMove) )
13504         /* White made time control */
13505         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13506         /* [HGM] time odds: correct new time quota for time odds! */
13507                                             / WhitePlayer()->timeOdds;
13508       else
13509         /* Black made time control */
13510         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13511                                             / WhitePlayer()->other->timeOdds;
13512 }
13513
13514 void
13515 DisplayBothClocks()
13516 {
13517     int wom = gameMode == EditPosition ?
13518       !blackPlaysFirst : WhiteOnMove(currentMove);
13519     DisplayWhiteClock(whiteTimeRemaining, wom);
13520     DisplayBlackClock(blackTimeRemaining, !wom);
13521 }
13522
13523
13524 /* Timekeeping seems to be a portability nightmare.  I think everyone
13525    has ftime(), but I'm really not sure, so I'm including some ifdefs
13526    to use other calls if you don't.  Clocks will be less accurate if
13527    you have neither ftime nor gettimeofday.
13528 */
13529
13530 /* VS 2008 requires the #include outside of the function */
13531 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13532 #include <sys/timeb.h>
13533 #endif
13534
13535 /* Get the current time as a TimeMark */
13536 void
13537 GetTimeMark(tm)
13538      TimeMark *tm;
13539 {
13540 #if HAVE_GETTIMEOFDAY
13541
13542     struct timeval timeVal;
13543     struct timezone timeZone;
13544
13545     gettimeofday(&timeVal, &timeZone);
13546     tm->sec = (long) timeVal.tv_sec; 
13547     tm->ms = (int) (timeVal.tv_usec / 1000L);
13548
13549 #else /*!HAVE_GETTIMEOFDAY*/
13550 #if HAVE_FTIME
13551
13552 // include <sys/timeb.h> / moved to just above start of function
13553     struct timeb timeB;
13554
13555     ftime(&timeB);
13556     tm->sec = (long) timeB.time;
13557     tm->ms = (int) timeB.millitm;
13558
13559 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13560     tm->sec = (long) time(NULL);
13561     tm->ms = 0;
13562 #endif
13563 #endif
13564 }
13565
13566 /* Return the difference in milliseconds between two
13567    time marks.  We assume the difference will fit in a long!
13568 */
13569 long
13570 SubtractTimeMarks(tm2, tm1)
13571      TimeMark *tm2, *tm1;
13572 {
13573     return 1000L*(tm2->sec - tm1->sec) +
13574            (long) (tm2->ms - tm1->ms);
13575 }
13576
13577
13578 /*
13579  * Code to manage the game clocks.
13580  *
13581  * In tournament play, black starts the clock and then white makes a move.
13582  * We give the human user a slight advantage if he is playing white---the
13583  * clocks don't run until he makes his first move, so it takes zero time.
13584  * Also, we don't account for network lag, so we could get out of sync
13585  * with GNU Chess's clock -- but then, referees are always right.  
13586  */
13587
13588 static TimeMark tickStartTM;
13589 static long intendedTickLength;
13590
13591 long
13592 NextTickLength(timeRemaining)
13593      long timeRemaining;
13594 {
13595     long nominalTickLength, nextTickLength;
13596
13597     if (timeRemaining > 0L && timeRemaining <= 10000L)
13598       nominalTickLength = 100L;
13599     else
13600       nominalTickLength = 1000L;
13601     nextTickLength = timeRemaining % nominalTickLength;
13602     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13603
13604     return nextTickLength;
13605 }
13606
13607 /* Adjust clock one minute up or down */
13608 void
13609 AdjustClock(Boolean which, int dir)
13610 {
13611     if(which) blackTimeRemaining += 60000*dir;
13612     else      whiteTimeRemaining += 60000*dir;
13613     DisplayBothClocks();
13614 }
13615
13616 /* Stop clocks and reset to a fresh time control */
13617 void
13618 ResetClocks() 
13619 {
13620     (void) StopClockTimer();
13621     if (appData.icsActive) {
13622         whiteTimeRemaining = blackTimeRemaining = 0;
13623     } else if (searchTime) {
13624         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13625         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13626     } else { /* [HGM] correct new time quote for time odds */
13627         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13628         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13629     }
13630     if (whiteFlag || blackFlag) {
13631         DisplayTitle("");
13632         whiteFlag = blackFlag = FALSE;
13633     }
13634     DisplayBothClocks();
13635 }
13636
13637 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13638
13639 /* Decrement running clock by amount of time that has passed */
13640 void
13641 DecrementClocks()
13642 {
13643     long timeRemaining;
13644     long lastTickLength, fudge;
13645     TimeMark now;
13646
13647     if (!appData.clockMode) return;
13648     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13649         
13650     GetTimeMark(&now);
13651
13652     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13653
13654     /* Fudge if we woke up a little too soon */
13655     fudge = intendedTickLength - lastTickLength;
13656     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13657
13658     if (WhiteOnMove(forwardMostMove)) {
13659         if(whiteNPS >= 0) lastTickLength = 0;
13660         timeRemaining = whiteTimeRemaining -= lastTickLength;
13661         DisplayWhiteClock(whiteTimeRemaining - fudge,
13662                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13663     } else {
13664         if(blackNPS >= 0) lastTickLength = 0;
13665         timeRemaining = blackTimeRemaining -= lastTickLength;
13666         DisplayBlackClock(blackTimeRemaining - fudge,
13667                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13668     }
13669
13670     if (CheckFlags()) return;
13671         
13672     tickStartTM = now;
13673     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13674     StartClockTimer(intendedTickLength);
13675
13676     /* if the time remaining has fallen below the alarm threshold, sound the
13677      * alarm. if the alarm has sounded and (due to a takeback or time control
13678      * with increment) the time remaining has increased to a level above the
13679      * threshold, reset the alarm so it can sound again. 
13680      */
13681     
13682     if (appData.icsActive && appData.icsAlarm) {
13683
13684         /* make sure we are dealing with the user's clock */
13685         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13686                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13687            )) return;
13688
13689         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13690             alarmSounded = FALSE;
13691         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13692             PlayAlarmSound();
13693             alarmSounded = TRUE;
13694         }
13695     }
13696 }
13697
13698
13699 /* A player has just moved, so stop the previously running
13700    clock and (if in clock mode) start the other one.
13701    We redisplay both clocks in case we're in ICS mode, because
13702    ICS gives us an update to both clocks after every move.
13703    Note that this routine is called *after* forwardMostMove
13704    is updated, so the last fractional tick must be subtracted
13705    from the color that is *not* on move now.
13706 */
13707 void
13708 SwitchClocks()
13709 {
13710     long lastTickLength;
13711     TimeMark now;
13712     int flagged = FALSE;
13713
13714     GetTimeMark(&now);
13715
13716     if (StopClockTimer() && appData.clockMode) {
13717         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13718         if (WhiteOnMove(forwardMostMove)) {
13719             if(blackNPS >= 0) lastTickLength = 0;
13720             blackTimeRemaining -= lastTickLength;
13721            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13722 //         if(pvInfoList[forwardMostMove-1].time == -1)
13723                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13724                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13725         } else {
13726            if(whiteNPS >= 0) lastTickLength = 0;
13727            whiteTimeRemaining -= lastTickLength;
13728            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13729 //         if(pvInfoList[forwardMostMove-1].time == -1)
13730                  pvInfoList[forwardMostMove-1].time = 
13731                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13732         }
13733         flagged = CheckFlags();
13734     }
13735     CheckTimeControl();
13736
13737     if (flagged || !appData.clockMode) return;
13738
13739     switch (gameMode) {
13740       case MachinePlaysBlack:
13741       case MachinePlaysWhite:
13742       case BeginningOfGame:
13743         if (pausing) return;
13744         break;
13745
13746       case EditGame:
13747       case PlayFromGameFile:
13748       case IcsExamining:
13749         return;
13750
13751       default:
13752         break;
13753     }
13754
13755     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13756         if(WhiteOnMove(forwardMostMove))
13757              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13758         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13759     }
13760
13761     tickStartTM = now;
13762     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13763       whiteTimeRemaining : blackTimeRemaining);
13764     StartClockTimer(intendedTickLength);
13765 }
13766         
13767
13768 /* Stop both clocks */
13769 void
13770 StopClocks()
13771 {       
13772     long lastTickLength;
13773     TimeMark now;
13774
13775     if (!StopClockTimer()) return;
13776     if (!appData.clockMode) return;
13777
13778     GetTimeMark(&now);
13779
13780     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13781     if (WhiteOnMove(forwardMostMove)) {
13782         if(whiteNPS >= 0) lastTickLength = 0;
13783         whiteTimeRemaining -= lastTickLength;
13784         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13785     } else {
13786         if(blackNPS >= 0) lastTickLength = 0;
13787         blackTimeRemaining -= lastTickLength;
13788         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13789     }
13790     CheckFlags();
13791 }
13792         
13793 /* Start clock of player on move.  Time may have been reset, so
13794    if clock is already running, stop and restart it. */
13795 void
13796 StartClocks()
13797 {
13798     (void) StopClockTimer(); /* in case it was running already */
13799     DisplayBothClocks();
13800     if (CheckFlags()) return;
13801
13802     if (!appData.clockMode) return;
13803     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13804
13805     GetTimeMark(&tickStartTM);
13806     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13807       whiteTimeRemaining : blackTimeRemaining);
13808
13809    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13810     whiteNPS = blackNPS = -1; 
13811     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13812        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13813         whiteNPS = first.nps;
13814     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13815        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13816         blackNPS = first.nps;
13817     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13818         whiteNPS = second.nps;
13819     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13820         blackNPS = second.nps;
13821     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13822
13823     StartClockTimer(intendedTickLength);
13824 }
13825
13826 char *
13827 TimeString(ms)
13828      long ms;
13829 {
13830     long second, minute, hour, day;
13831     char *sign = "";
13832     static char buf[32];
13833     
13834     if (ms > 0 && ms <= 9900) {
13835       /* convert milliseconds to tenths, rounding up */
13836       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13837
13838       sprintf(buf, " %03.1f ", tenths/10.0);
13839       return buf;
13840     }
13841
13842     /* convert milliseconds to seconds, rounding up */
13843     /* use floating point to avoid strangeness of integer division
13844        with negative dividends on many machines */
13845     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13846
13847     if (second < 0) {
13848         sign = "-";
13849         second = -second;
13850     }
13851     
13852     day = second / (60 * 60 * 24);
13853     second = second % (60 * 60 * 24);
13854     hour = second / (60 * 60);
13855     second = second % (60 * 60);
13856     minute = second / 60;
13857     second = second % 60;
13858     
13859     if (day > 0)
13860       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13861               sign, day, hour, minute, second);
13862     else if (hour > 0)
13863       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13864     else
13865       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13866     
13867     return buf;
13868 }
13869
13870
13871 /*
13872  * This is necessary because some C libraries aren't ANSI C compliant yet.
13873  */
13874 char *
13875 StrStr(string, match)
13876      char *string, *match;
13877 {
13878     int i, length;
13879     
13880     length = strlen(match);
13881     
13882     for (i = strlen(string) - length; i >= 0; i--, string++)
13883       if (!strncmp(match, string, length))
13884         return string;
13885     
13886     return NULL;
13887 }
13888
13889 char *
13890 StrCaseStr(string, match)
13891      char *string, *match;
13892 {
13893     int i, j, length;
13894     
13895     length = strlen(match);
13896     
13897     for (i = strlen(string) - length; i >= 0; i--, string++) {
13898         for (j = 0; j < length; j++) {
13899             if (ToLower(match[j]) != ToLower(string[j]))
13900               break;
13901         }
13902         if (j == length) return string;
13903     }
13904
13905     return NULL;
13906 }
13907
13908 #ifndef _amigados
13909 int
13910 StrCaseCmp(s1, s2)
13911      char *s1, *s2;
13912 {
13913     char c1, c2;
13914     
13915     for (;;) {
13916         c1 = ToLower(*s1++);
13917         c2 = ToLower(*s2++);
13918         if (c1 > c2) return 1;
13919         if (c1 < c2) return -1;
13920         if (c1 == NULLCHAR) return 0;
13921     }
13922 }
13923
13924
13925 int
13926 ToLower(c)
13927      int c;
13928 {
13929     return isupper(c) ? tolower(c) : c;
13930 }
13931
13932
13933 int
13934 ToUpper(c)
13935      int c;
13936 {
13937     return islower(c) ? toupper(c) : c;
13938 }
13939 #endif /* !_amigados    */
13940
13941 char *
13942 StrSave(s)
13943      char *s;
13944 {
13945     char *ret;
13946
13947     if ((ret = (char *) malloc(strlen(s) + 1))) {
13948         strcpy(ret, s);
13949     }
13950     return ret;
13951 }
13952
13953 char *
13954 StrSavePtr(s, savePtr)
13955      char *s, **savePtr;
13956 {
13957     if (*savePtr) {
13958         free(*savePtr);
13959     }
13960     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13961         strcpy(*savePtr, s);
13962     }
13963     return(*savePtr);
13964 }
13965
13966 char *
13967 PGNDate()
13968 {
13969     time_t clock;
13970     struct tm *tm;
13971     char buf[MSG_SIZ];
13972
13973     clock = time((time_t *)NULL);
13974     tm = localtime(&clock);
13975     sprintf(buf, "%04d.%02d.%02d",
13976             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13977     return StrSave(buf);
13978 }
13979
13980
13981 char *
13982 PositionToFEN(move, overrideCastling)
13983      int move;
13984      char *overrideCastling;
13985 {
13986     int i, j, fromX, fromY, toX, toY;
13987     int whiteToPlay;
13988     char buf[128];
13989     char *p, *q;
13990     int emptycount;
13991     ChessSquare piece;
13992
13993     whiteToPlay = (gameMode == EditPosition) ?
13994       !blackPlaysFirst : (move % 2 == 0);
13995     p = buf;
13996
13997     /* Piece placement data */
13998     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13999         emptycount = 0;
14000         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14001             if (boards[move][i][j] == EmptySquare) {
14002                 emptycount++;
14003             } else { ChessSquare piece = boards[move][i][j];
14004                 if (emptycount > 0) {
14005                     if(emptycount<10) /* [HGM] can be >= 10 */
14006                         *p++ = '0' + emptycount;
14007                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14008                     emptycount = 0;
14009                 }
14010                 if(PieceToChar(piece) == '+') {
14011                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14012                     *p++ = '+';
14013                     piece = (ChessSquare)(DEMOTED piece);
14014                 } 
14015                 *p++ = PieceToChar(piece);
14016                 if(p[-1] == '~') {
14017                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14018                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14019                     *p++ = '~';
14020                 }
14021             }
14022         }
14023         if (emptycount > 0) {
14024             if(emptycount<10) /* [HGM] can be >= 10 */
14025                 *p++ = '0' + emptycount;
14026             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14027             emptycount = 0;
14028         }
14029         *p++ = '/';
14030     }
14031     *(p - 1) = ' ';
14032
14033     /* [HGM] print Crazyhouse or Shogi holdings */
14034     if( gameInfo.holdingsWidth ) {
14035         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14036         q = p;
14037         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14038             piece = boards[move][i][BOARD_WIDTH-1];
14039             if( piece != EmptySquare )
14040               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14041                   *p++ = PieceToChar(piece);
14042         }
14043         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14044             piece = boards[move][BOARD_HEIGHT-i-1][0];
14045             if( piece != EmptySquare )
14046               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14047                   *p++ = PieceToChar(piece);
14048         }
14049
14050         if( q == p ) *p++ = '-';
14051         *p++ = ']';
14052         *p++ = ' ';
14053     }
14054
14055     /* Active color */
14056     *p++ = whiteToPlay ? 'w' : 'b';
14057     *p++ = ' ';
14058
14059   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14060     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14061   } else {
14062   if(nrCastlingRights) {
14063      q = p;
14064      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14065        /* [HGM] write directly from rights */
14066            if(boards[move][CASTLING][2] != NoRights &&
14067               boards[move][CASTLING][0] != NoRights   )
14068                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14069            if(boards[move][CASTLING][2] != NoRights &&
14070               boards[move][CASTLING][1] != NoRights   )
14071                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14072            if(boards[move][CASTLING][5] != NoRights &&
14073               boards[move][CASTLING][3] != NoRights   )
14074                 *p++ = boards[move][CASTLING][3] + AAA;
14075            if(boards[move][CASTLING][5] != NoRights &&
14076               boards[move][CASTLING][4] != NoRights   )
14077                 *p++ = boards[move][CASTLING][4] + AAA;
14078      } else {
14079
14080         /* [HGM] write true castling rights */
14081         if( nrCastlingRights == 6 ) {
14082             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14083                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14084             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14085                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14086             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14087                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14088             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14089                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14090         }
14091      }
14092      if (q == p) *p++ = '-'; /* No castling rights */
14093      *p++ = ' ';
14094   }
14095
14096   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14097      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14098     /* En passant target square */
14099     if (move > backwardMostMove) {
14100         fromX = moveList[move - 1][0] - AAA;
14101         fromY = moveList[move - 1][1] - ONE;
14102         toX = moveList[move - 1][2] - AAA;
14103         toY = moveList[move - 1][3] - ONE;
14104         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14105             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14106             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14107             fromX == toX) {
14108             /* 2-square pawn move just happened */
14109             *p++ = toX + AAA;
14110             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14111         } else {
14112             *p++ = '-';
14113         }
14114     } else if(move == backwardMostMove) {
14115         // [HGM] perhaps we should always do it like this, and forget the above?
14116         if((signed char)boards[move][EP_STATUS] >= 0) {
14117             *p++ = boards[move][EP_STATUS] + AAA;
14118             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14119         } else {
14120             *p++ = '-';
14121         }
14122     } else {
14123         *p++ = '-';
14124     }
14125     *p++ = ' ';
14126   }
14127   }
14128
14129     /* [HGM] find reversible plies */
14130     {   int i = 0, j=move;
14131
14132         if (appData.debugMode) { int k;
14133             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14134             for(k=backwardMostMove; k<=forwardMostMove; k++)
14135                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14136
14137         }
14138
14139         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14140         if( j == backwardMostMove ) i += initialRulePlies;
14141         sprintf(p, "%d ", i);
14142         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14143     }
14144     /* Fullmove number */
14145     sprintf(p, "%d", (move / 2) + 1);
14146     
14147     return StrSave(buf);
14148 }
14149
14150 Boolean
14151 ParseFEN(board, blackPlaysFirst, fen)
14152     Board board;
14153      int *blackPlaysFirst;
14154      char *fen;
14155 {
14156     int i, j;
14157     char *p;
14158     int emptycount;
14159     ChessSquare piece;
14160
14161     p = fen;
14162
14163     /* [HGM] by default clear Crazyhouse holdings, if present */
14164     if(gameInfo.holdingsWidth) {
14165        for(i=0; i<BOARD_HEIGHT; i++) {
14166            board[i][0]             = EmptySquare; /* black holdings */
14167            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14168            board[i][1]             = (ChessSquare) 0; /* black counts */
14169            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14170        }
14171     }
14172
14173     /* Piece placement data */
14174     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14175         j = 0;
14176         for (;;) {
14177             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14178                 if (*p == '/') p++;
14179                 emptycount = gameInfo.boardWidth - j;
14180                 while (emptycount--)
14181                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14182                 break;
14183 #if(BOARD_FILES >= 10)
14184             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14185                 p++; emptycount=10;
14186                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14187                 while (emptycount--)
14188                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14189 #endif
14190             } else if (isdigit(*p)) {
14191                 emptycount = *p++ - '0';
14192                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14193                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14194                 while (emptycount--)
14195                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14196             } else if (*p == '+' || isalpha(*p)) {
14197                 if (j >= gameInfo.boardWidth) return FALSE;
14198                 if(*p=='+') {
14199                     piece = CharToPiece(*++p);
14200                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14201                     piece = (ChessSquare) (PROMOTED piece ); p++;
14202                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14203                 } else piece = CharToPiece(*p++);
14204
14205                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14206                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14207                     piece = (ChessSquare) (PROMOTED piece);
14208                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14209                     p++;
14210                 }
14211                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14212             } else {
14213                 return FALSE;
14214             }
14215         }
14216     }
14217     while (*p == '/' || *p == ' ') p++;
14218
14219     /* [HGM] look for Crazyhouse holdings here */
14220     while(*p==' ') p++;
14221     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14222         if(*p == '[') p++;
14223         if(*p == '-' ) *p++; /* empty holdings */ else {
14224             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14225             /* if we would allow FEN reading to set board size, we would   */
14226             /* have to add holdings and shift the board read so far here   */
14227             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14228                 *p++;
14229                 if((int) piece >= (int) BlackPawn ) {
14230                     i = (int)piece - (int)BlackPawn;
14231                     i = PieceToNumber((ChessSquare)i);
14232                     if( i >= gameInfo.holdingsSize ) return FALSE;
14233                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14234                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14235                 } else {
14236                     i = (int)piece - (int)WhitePawn;
14237                     i = PieceToNumber((ChessSquare)i);
14238                     if( i >= gameInfo.holdingsSize ) return FALSE;
14239                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14240                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14241                 }
14242             }
14243         }
14244         if(*p == ']') *p++;
14245     }
14246
14247     while(*p == ' ') p++;
14248
14249     /* Active color */
14250     switch (*p++) {
14251       case 'w':
14252         *blackPlaysFirst = FALSE;
14253         break;
14254       case 'b': 
14255         *blackPlaysFirst = TRUE;
14256         break;
14257       default:
14258         return FALSE;
14259     }
14260
14261     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14262     /* return the extra info in global variiables             */
14263
14264     /* set defaults in case FEN is incomplete */
14265     board[EP_STATUS] = EP_UNKNOWN;
14266     for(i=0; i<nrCastlingRights; i++ ) {
14267         board[CASTLING][i] =
14268             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14269     }   /* assume possible unless obviously impossible */
14270     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14271     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14272     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14273                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14274     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14275     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14276     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14277                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14278     FENrulePlies = 0;
14279
14280     while(*p==' ') p++;
14281     if(nrCastlingRights) {
14282       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14283           /* castling indicator present, so default becomes no castlings */
14284           for(i=0; i<nrCastlingRights; i++ ) {
14285                  board[CASTLING][i] = NoRights;
14286           }
14287       }
14288       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14289              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14290              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14291              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14292         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14293
14294         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14295             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14296             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14297         }
14298         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14299             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14300         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14301                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14302         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14303                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14304         switch(c) {
14305           case'K':
14306               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14307               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14308               board[CASTLING][2] = whiteKingFile;
14309               break;
14310           case'Q':
14311               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14312               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14313               board[CASTLING][2] = whiteKingFile;
14314               break;
14315           case'k':
14316               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14317               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14318               board[CASTLING][5] = blackKingFile;
14319               break;
14320           case'q':
14321               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14322               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14323               board[CASTLING][5] = blackKingFile;
14324           case '-':
14325               break;
14326           default: /* FRC castlings */
14327               if(c >= 'a') { /* black rights */
14328                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14329                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14330                   if(i == BOARD_RGHT) break;
14331                   board[CASTLING][5] = i;
14332                   c -= AAA;
14333                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14334                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14335                   if(c > i)
14336                       board[CASTLING][3] = c;
14337                   else
14338                       board[CASTLING][4] = c;
14339               } else { /* white rights */
14340                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14341                     if(board[0][i] == WhiteKing) break;
14342                   if(i == BOARD_RGHT) break;
14343                   board[CASTLING][2] = i;
14344                   c -= AAA - 'a' + 'A';
14345                   if(board[0][c] >= WhiteKing) break;
14346                   if(c > i)
14347                       board[CASTLING][0] = c;
14348                   else
14349                       board[CASTLING][1] = c;
14350               }
14351         }
14352       }
14353       for(i=0; i<nrCastlingRights; i++)
14354         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14355     if (appData.debugMode) {
14356         fprintf(debugFP, "FEN castling rights:");
14357         for(i=0; i<nrCastlingRights; i++)
14358         fprintf(debugFP, " %d", board[CASTLING][i]);
14359         fprintf(debugFP, "\n");
14360     }
14361
14362       while(*p==' ') p++;
14363     }
14364
14365     /* read e.p. field in games that know e.p. capture */
14366     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14367        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14368       if(*p=='-') {
14369         p++; board[EP_STATUS] = EP_NONE;
14370       } else {
14371          char c = *p++ - AAA;
14372
14373          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14374          if(*p >= '0' && *p <='9') *p++;
14375          board[EP_STATUS] = c;
14376       }
14377     }
14378
14379
14380     if(sscanf(p, "%d", &i) == 1) {
14381         FENrulePlies = i; /* 50-move ply counter */
14382         /* (The move number is still ignored)    */
14383     }
14384
14385     return TRUE;
14386 }
14387       
14388 void
14389 EditPositionPasteFEN(char *fen)
14390 {
14391   if (fen != NULL) {
14392     Board initial_position;
14393
14394     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14395       DisplayError(_("Bad FEN position in clipboard"), 0);
14396       return ;
14397     } else {
14398       int savedBlackPlaysFirst = blackPlaysFirst;
14399       EditPositionEvent();
14400       blackPlaysFirst = savedBlackPlaysFirst;
14401       CopyBoard(boards[0], initial_position);
14402       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14403       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14404       DisplayBothClocks();
14405       DrawPosition(FALSE, boards[currentMove]);
14406     }
14407   }
14408 }
14409
14410 static char cseq[12] = "\\   ";
14411
14412 Boolean set_cont_sequence(char *new_seq)
14413 {
14414     int len;
14415     Boolean ret;
14416
14417     // handle bad attempts to set the sequence
14418         if (!new_seq)
14419                 return 0; // acceptable error - no debug
14420
14421     len = strlen(new_seq);
14422     ret = (len > 0) && (len < sizeof(cseq));
14423     if (ret)
14424         strcpy(cseq, new_seq);
14425     else if (appData.debugMode)
14426         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14427     return ret;
14428 }
14429
14430 /*
14431     reformat a source message so words don't cross the width boundary.  internal
14432     newlines are not removed.  returns the wrapped size (no null character unless
14433     included in source message).  If dest is NULL, only calculate the size required
14434     for the dest buffer.  lp argument indicats line position upon entry, and it's
14435     passed back upon exit.
14436 */
14437 int wrap(char *dest, char *src, int count, int width, int *lp)
14438 {
14439     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14440
14441     cseq_len = strlen(cseq);
14442     old_line = line = *lp;
14443     ansi = len = clen = 0;
14444
14445     for (i=0; i < count; i++)
14446     {
14447         if (src[i] == '\033')
14448             ansi = 1;
14449
14450         // if we hit the width, back up
14451         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14452         {
14453             // store i & len in case the word is too long
14454             old_i = i, old_len = len;
14455
14456             // find the end of the last word
14457             while (i && src[i] != ' ' && src[i] != '\n')
14458             {
14459                 i--;
14460                 len--;
14461             }
14462
14463             // word too long?  restore i & len before splitting it
14464             if ((old_i-i+clen) >= width)
14465             {
14466                 i = old_i;
14467                 len = old_len;
14468             }
14469
14470             // extra space?
14471             if (i && src[i-1] == ' ')
14472                 len--;
14473
14474             if (src[i] != ' ' && src[i] != '\n')
14475             {
14476                 i--;
14477                 if (len)
14478                     len--;
14479             }
14480
14481             // now append the newline and continuation sequence
14482             if (dest)
14483                 dest[len] = '\n';
14484             len++;
14485             if (dest)
14486                 strncpy(dest+len, cseq, cseq_len);
14487             len += cseq_len;
14488             line = cseq_len;
14489             clen = cseq_len;
14490             continue;
14491         }
14492
14493         if (dest)
14494             dest[len] = src[i];
14495         len++;
14496         if (!ansi)
14497             line++;
14498         if (src[i] == '\n')
14499             line = 0;
14500         if (src[i] == 'm')
14501             ansi = 0;
14502     }
14503     if (dest && appData.debugMode)
14504     {
14505         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14506             count, width, line, len, *lp);
14507         show_bytes(debugFP, src, count);
14508         fprintf(debugFP, "\ndest: ");
14509         show_bytes(debugFP, dest, len);
14510         fprintf(debugFP, "\n");
14511     }
14512     *lp = dest ? line : old_line;
14513
14514     return len;
14515 }
14516
14517 // [HGM] vari: routines for shelving variations
14518
14519 void 
14520 PushTail(int firstMove, int lastMove)
14521 {
14522         int i, j, nrMoves = lastMove - firstMove;
14523
14524         if(appData.icsActive) { // only in local mode
14525                 forwardMostMove = currentMove; // mimic old ICS behavior
14526                 return;
14527         }
14528         if(storedGames >= MAX_VARIATIONS-1) return;
14529
14530         // push current tail of game on stack
14531         savedResult[storedGames] = gameInfo.result;
14532         savedDetails[storedGames] = gameInfo.resultDetails;
14533         gameInfo.resultDetails = NULL;
14534         savedFirst[storedGames] = firstMove;
14535         savedLast [storedGames] = lastMove;
14536         savedFramePtr[storedGames] = framePtr;
14537         framePtr -= nrMoves; // reserve space for the boards
14538         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14539             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14540             for(j=0; j<MOVE_LEN; j++)
14541                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14542             for(j=0; j<2*MOVE_LEN; j++)
14543                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14544             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14545             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14546             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14547             pvInfoList[firstMove+i-1].depth = 0;
14548             commentList[framePtr+i] = commentList[firstMove+i];
14549             commentList[firstMove+i] = NULL;
14550         }
14551
14552         storedGames++;
14553         forwardMostMove = currentMove; // truncte game so we can start variation
14554         if(storedGames == 1) GreyRevert(FALSE);
14555 }
14556
14557 Boolean
14558 PopTail(Boolean annotate)
14559 {
14560         int i, j, nrMoves;
14561         char buf[8000], moveBuf[20];
14562
14563         if(appData.icsActive) return FALSE; // only in local mode
14564         if(!storedGames) return FALSE; // sanity
14565
14566         storedGames--;
14567         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14568         nrMoves = savedLast[storedGames] - currentMove;
14569         if(annotate) {
14570                 int cnt = 10;
14571                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14572                 else strcpy(buf, "(");
14573                 for(i=currentMove; i<forwardMostMove; i++) {
14574                         if(WhiteOnMove(i))
14575                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14576                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14577                         strcat(buf, moveBuf);
14578                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14579                 }
14580                 strcat(buf, ")");
14581         }
14582         for(i=1; i<nrMoves; i++) { // copy last variation back
14583             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14584             for(j=0; j<MOVE_LEN; j++)
14585                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14586             for(j=0; j<2*MOVE_LEN; j++)
14587                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14588             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14589             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14590             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14591             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14592             commentList[currentMove+i] = commentList[framePtr+i];
14593             commentList[framePtr+i] = NULL;
14594         }
14595         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14596         framePtr = savedFramePtr[storedGames];
14597         gameInfo.result = savedResult[storedGames];
14598         if(gameInfo.resultDetails != NULL) {
14599             free(gameInfo.resultDetails);
14600       }
14601         gameInfo.resultDetails = savedDetails[storedGames];
14602         forwardMostMove = currentMove + nrMoves;
14603         if(storedGames == 0) GreyRevert(TRUE);
14604         return TRUE;
14605 }
14606
14607 void 
14608 CleanupTail()
14609 {       // remove all shelved variations
14610         int i;
14611         for(i=0; i<storedGames; i++) {
14612             if(savedDetails[i])
14613                 free(savedDetails[i]);
14614             savedDetails[i] = NULL;
14615         }
14616         for(i=framePtr; i<MAX_MOVES; i++) {
14617                 if(commentList[i]) free(commentList[i]);
14618                 commentList[i] = NULL;
14619         }
14620         framePtr = MAX_MOVES-1;
14621         storedGames = 0;
14622 }