Merge branch 'master' into gtk
[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 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
239 int endPV = -1;
240 static int exiting = 0; /* [HGM] moved to top */
241 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
242 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
243 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
244 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
245 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
246 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
251 int chattingPartner;
252 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
253
254 /* States for ics_getting_history */
255 #define H_FALSE 0
256 #define H_REQUESTED 1
257 #define H_GOT_REQ_HEADER 2
258 #define H_GOT_UNREQ_HEADER 3
259 #define H_GETTING_MOVES 4
260 #define H_GOT_UNWANTED_HEADER 5
261
262 /* whosays values for GameEnds */
263 #define GE_ICS 0
264 #define GE_ENGINE 1
265 #define GE_PLAYER 2
266 #define GE_FILE 3
267 #define GE_XBOARD 4
268 #define GE_ENGINE1 5
269 #define GE_ENGINE2 6
270
271 /* Maximum number of games in a cmail message */
272 #define CMAIL_MAX_GAMES 20
273
274 /* Different types of move when calling RegisterMove */
275 #define CMAIL_MOVE   0
276 #define CMAIL_RESIGN 1
277 #define CMAIL_DRAW   2
278 #define CMAIL_ACCEPT 3
279
280 /* Different types of result to remember for each game */
281 #define CMAIL_NOT_RESULT 0
282 #define CMAIL_OLD_RESULT 1
283 #define CMAIL_NEW_RESULT 2
284
285 /* Telnet protocol constants */
286 #define TN_WILL 0373
287 #define TN_WONT 0374
288 #define TN_DO   0375
289 #define TN_DONT 0376
290 #define TN_IAC  0377
291 #define TN_ECHO 0001
292 #define TN_SGA  0003
293 #define TN_PORT 23
294
295 /* [AS] */
296 static char * safeStrCpy( char * dst, const char * src, size_t count )
297 {
298     assert( dst != NULL );
299     assert( src != NULL );
300     assert( count > 0 );
301
302     strncpy( dst, src, count );
303     dst[ count-1 ] = '\0';
304     return dst;
305 }
306
307 /* Some compiler can't cast u64 to double
308  * This function do the job for us:
309
310  * We use the highest bit for cast, this only
311  * works if the highest bit is not
312  * in use (This should not happen)
313  *
314  * We used this for all compiler
315  */
316 double
317 u64ToDouble(u64 value)
318 {
319   double r;
320   u64 tmp = value & u64Const(0x7fffffffffffffff);
321   r = (double)(s64)tmp;
322   if (value & u64Const(0x8000000000000000))
323        r +=  9.2233720368547758080e18; /* 2^63 */
324  return r;
325 }
326
327 /* Fake up flags for now, as we aren't keeping track of castling
328    availability yet. [HGM] Change of logic: the flag now only
329    indicates the type of castlings allowed by the rule of the game.
330    The actual rights themselves are maintained in the array
331    castlingRights, as part of the game history, and are not probed
332    by this function.
333  */
334 int
335 PosFlags(index)
336 {
337   int flags = F_ALL_CASTLE_OK;
338   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
339   switch (gameInfo.variant) {
340   case VariantSuicide:
341     flags &= ~F_ALL_CASTLE_OK;
342   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
343     flags |= F_IGNORE_CHECK;
344   case VariantLosers:
345     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
346     break;
347   case VariantAtomic:
348     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
349     break;
350   case VariantKriegspiel:
351     flags |= F_KRIEGSPIEL_CAPTURE;
352     break;
353   case VariantCapaRandom:
354   case VariantFischeRandom:
355     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
356   case VariantNoCastle:
357   case VariantShatranj:
358   case VariantCourier:
359   case VariantMakruk:
360     flags &= ~F_ALL_CASTLE_OK;
361     break;
362   default:
363     break;
364   }
365   return flags;
366 }
367
368 FILE *gameFileFP, *debugFP;
369
370 /*
371     [AS] Note: sometimes, the sscanf() function is used to parse the input
372     into a fixed-size buffer. Because of this, we must be prepared to
373     receive strings as long as the size of the input buffer, which is currently
374     set to 4K for Windows and 8K for the rest.
375     So, we must either allocate sufficiently large buffers here, or
376     reduce the size of the input buffer in the input reading part.
377 */
378
379 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
380 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
381 char thinkOutput1[MSG_SIZ*10];
382
383 ChessProgramState first, second;
384
385 /* premove variables */
386 int premoveToX = 0;
387 int premoveToY = 0;
388 int premoveFromX = 0;
389 int premoveFromY = 0;
390 int premovePromoChar = 0;
391 int gotPremove = 0;
392 Boolean alarmSounded;
393 /* end premove variables */
394
395 char *ics_prefix = "$";
396 int ics_type = ICS_GENERIC;
397
398 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
399 int pauseExamForwardMostMove = 0;
400 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
401 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
402 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
403 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
404 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
405 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
406 int whiteFlag = FALSE, blackFlag = FALSE;
407 int userOfferedDraw = FALSE;
408 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
409 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
410 int cmailMoveType[CMAIL_MAX_GAMES];
411 long ics_clock_paused = 0;
412 ProcRef icsPR = NoProc, cmailPR = NoProc;
413 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
414 GameMode gameMode = BeginningOfGame;
415 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
416 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
417 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
418 int hiddenThinkOutputState = 0; /* [AS] */
419 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
420 int adjudicateLossPlies = 6;
421 char white_holding[64], black_holding[64];
422 TimeMark lastNodeCountTime;
423 long lastNodeCount=0;
424 int have_sent_ICS_logon = 0;
425 int movesPerSession;
426 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
427 long timeControl_2; /* [AS] Allow separate time controls */
428 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
429 long timeRemaining[2][MAX_MOVES];
430 int matchGame = 0;
431 TimeMark programStartTime;
432 char ics_handle[MSG_SIZ];
433 int have_set_title = 0;
434
435 /* animateTraining preserves the state of appData.animate
436  * when Training mode is activated. This allows the
437  * response to be animated when appData.animate == TRUE and
438  * appData.animateDragging == TRUE.
439  */
440 Boolean animateTraining;
441
442 GameInfo gameInfo;
443
444 AppData appData;
445
446 Board boards[MAX_MOVES];
447 /* [HGM] Following 7 needed for accurate legality tests: */
448 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
449 signed char  initialRights[BOARD_FILES];
450 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
451 int   initialRulePlies, FENrulePlies;
452 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
453 int loadFlag = 0;
454 int shuffleOpenings;
455 int mute; // mute all sounds
456
457 // [HGM] vari: next 12 to save and restore variations
458 #define MAX_VARIATIONS 10
459 int framePtr = MAX_MOVES-1; // points to free stack entry
460 int storedGames = 0;
461 int savedFirst[MAX_VARIATIONS];
462 int savedLast[MAX_VARIATIONS];
463 int savedFramePtr[MAX_VARIATIONS];
464 char *savedDetails[MAX_VARIATIONS];
465 ChessMove savedResult[MAX_VARIATIONS];
466
467 void PushTail P((int firstMove, int lastMove));
468 Boolean PopTail P((Boolean annotate));
469 void CleanupTail P((void));
470
471 ChessSquare  FIDEArray[2][BOARD_FILES] = {
472     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
473         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
474     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
475         BlackKing, BlackBishop, BlackKnight, BlackRook }
476 };
477
478 ChessSquare twoKingsArray[2][BOARD_FILES] = {
479     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
480         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
481     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
482         BlackKing, BlackKing, BlackKnight, BlackRook }
483 };
484
485 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
486     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
487         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
488     { BlackRook, BlackMan, BlackBishop, BlackQueen,
489         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
490 };
491
492 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
493     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
494         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
495     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
496         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
497 };
498
499 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
500     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
501         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
502     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
503         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
504 };
505
506 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
507     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
508         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackMan, BlackFerz,
510         BlackKing, BlackMan, BlackKnight, BlackRook }
511 };
512
513
514 #if (BOARD_FILES>=10)
515 ChessSquare ShogiArray[2][BOARD_FILES] = {
516     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
517         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
518     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
519         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
520 };
521
522 ChessSquare XiangqiArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
524         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
526         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
527 };
528
529 ChessSquare CapablancaArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
531         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
533         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
534 };
535
536 ChessSquare GreatArray[2][BOARD_FILES] = {
537     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
538         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
539     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
540         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
541 };
542
543 ChessSquare JanusArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
545         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
546     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
547         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
548 };
549
550 #ifdef GOTHIC
551 ChessSquare GothicArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
553         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
555         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
556 };
557 #else // !GOTHIC
558 #define GothicArray CapablancaArray
559 #endif // !GOTHIC
560
561 #ifdef FALCON
562 ChessSquare FalconArray[2][BOARD_FILES] = {
563     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
564         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
566         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
567 };
568 #else // !FALCON
569 #define FalconArray CapablancaArray
570 #endif // !FALCON
571
572 #else // !(BOARD_FILES>=10)
573 #define XiangqiPosition FIDEArray
574 #define CapablancaArray FIDEArray
575 #define GothicArray FIDEArray
576 #define GreatArray FIDEArray
577 #endif // !(BOARD_FILES>=10)
578
579 #if (BOARD_FILES>=12)
580 ChessSquare CourierArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
582         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
584         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
585 };
586 #else // !(BOARD_FILES>=12)
587 #define CourierArray CapablancaArray
588 #endif // !(BOARD_FILES>=12)
589
590
591 Board initialPosition;
592
593
594 /* Convert str to a rating. Checks for special cases of "----",
595
596    "++++", etc. Also strips ()'s */
597 int
598 string_to_rating(str)
599   char *str;
600 {
601   while(*str && !isdigit(*str)) ++str;
602   if (!*str)
603     return 0;   /* One of the special "no rating" cases */
604   else
605     return atoi(str);
606 }
607
608 void
609 ClearProgramStats()
610 {
611     /* Init programStats */
612     programStats.movelist[0] = 0;
613     programStats.depth = 0;
614     programStats.nr_moves = 0;
615     programStats.moves_left = 0;
616     programStats.nodes = 0;
617     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
618     programStats.score = 0;
619     programStats.got_only_move = 0;
620     programStats.got_fail = 0;
621     programStats.line_is_book = 0;
622 }
623
624 void
625 InitBackEnd1()
626 {
627     int matched, min, sec;
628
629     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
630
631     GetTimeMark(&programStartTime);
632     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
633
634     ClearProgramStats();
635     programStats.ok_to_send = 1;
636     programStats.seen_stat = 0;
637
638     /*
639      * Initialize game list
640      */
641     ListNew(&gameList);
642
643
644     /*
645      * Internet chess server status
646      */
647     if (appData.icsActive) {
648         appData.matchMode = FALSE;
649         appData.matchGames = 0;
650 #if ZIPPY
651         appData.noChessProgram = !appData.zippyPlay;
652 #else
653         appData.zippyPlay = FALSE;
654         appData.zippyTalk = FALSE;
655         appData.noChessProgram = TRUE;
656 #endif
657         if (*appData.icsHelper != NULLCHAR) {
658             appData.useTelnet = TRUE;
659             appData.telnetProgram = appData.icsHelper;
660         }
661     } else {
662         appData.zippyTalk = appData.zippyPlay = FALSE;
663     }
664
665     /* [AS] Initialize pv info list [HGM] and game state */
666     {
667         int i, j;
668
669         for( i=0; i<=framePtr; i++ ) {
670             pvInfoList[i].depth = -1;
671             boards[i][EP_STATUS] = EP_NONE;
672             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
673         }
674     }
675
676     /*
677      * Parse timeControl resource
678      */
679     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
680                           appData.movesPerSession)) {
681         char buf[MSG_SIZ];
682         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
683         DisplayFatalError(buf, 0, 2);
684     }
685
686     /*
687      * Parse searchTime resource
688      */
689     if (*appData.searchTime != NULLCHAR) {
690         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
691         if (matched == 1) {
692             searchTime = min * 60;
693         } else if (matched == 2) {
694             searchTime = min * 60 + sec;
695         } else {
696             char buf[MSG_SIZ];
697             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
698             DisplayFatalError(buf, 0, 2);
699         }
700     }
701
702     /* [AS] Adjudication threshold */
703     adjudicateLossThreshold = appData.adjudicateLossThreshold;
704
705     first.which = "first";
706     second.which = "second";
707     first.maybeThinking = second.maybeThinking = FALSE;
708     first.pr = second.pr = NoProc;
709     first.isr = second.isr = NULL;
710     first.sendTime = second.sendTime = 2;
711     first.sendDrawOffers = 1;
712     if (appData.firstPlaysBlack) {
713         first.twoMachinesColor = "black\n";
714         second.twoMachinesColor = "white\n";
715     } else {
716         first.twoMachinesColor = "white\n";
717         second.twoMachinesColor = "black\n";
718     }
719     first.program = appData.firstChessProgram;
720     second.program = appData.secondChessProgram;
721     first.host = appData.firstHost;
722     second.host = appData.secondHost;
723     first.dir = appData.firstDirectory;
724     second.dir = appData.secondDirectory;
725     first.other = &second;
726     second.other = &first;
727     first.initString = appData.initString;
728     second.initString = appData.secondInitString;
729     first.computerString = appData.firstComputerString;
730     second.computerString = appData.secondComputerString;
731     first.useSigint = second.useSigint = TRUE;
732     first.useSigterm = second.useSigterm = TRUE;
733     first.reuse = appData.reuseFirst;
734     second.reuse = appData.reuseSecond;
735     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
736     second.nps = appData.secondNPS;
737     first.useSetboard = second.useSetboard = FALSE;
738     first.useSAN = second.useSAN = FALSE;
739     first.usePing = second.usePing = FALSE;
740     first.lastPing = second.lastPing = 0;
741     first.lastPong = second.lastPong = 0;
742     first.usePlayother = second.usePlayother = FALSE;
743     first.useColors = second.useColors = TRUE;
744     first.useUsermove = second.useUsermove = FALSE;
745     first.sendICS = second.sendICS = FALSE;
746     first.sendName = second.sendName = appData.icsActive;
747     first.sdKludge = second.sdKludge = FALSE;
748     first.stKludge = second.stKludge = FALSE;
749     TidyProgramName(first.program, first.host, first.tidy);
750     TidyProgramName(second.program, second.host, second.tidy);
751     first.matchWins = second.matchWins = 0;
752     strcpy(first.variants, appData.variant);
753     strcpy(second.variants, appData.variant);
754     first.analysisSupport = second.analysisSupport = 2; /* detect */
755     first.analyzing = second.analyzing = FALSE;
756     first.initDone = second.initDone = FALSE;
757
758     /* New features added by Tord: */
759     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
760     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
761     /* End of new features added by Tord. */
762     first.fenOverride  = appData.fenOverride1;
763     second.fenOverride = appData.fenOverride2;
764
765     /* [HGM] time odds: set factor for each machine */
766     first.timeOdds  = appData.firstTimeOdds;
767     second.timeOdds = appData.secondTimeOdds;
768     { float norm = 1;
769         if(appData.timeOddsMode) {
770             norm = first.timeOdds;
771             if(norm > second.timeOdds) norm = second.timeOdds;
772         }
773         first.timeOdds /= norm;
774         second.timeOdds /= norm;
775     }
776
777     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
778     first.accumulateTC = appData.firstAccumulateTC;
779     second.accumulateTC = appData.secondAccumulateTC;
780     first.maxNrOfSessions = second.maxNrOfSessions = 1;
781
782     /* [HGM] debug */
783     first.debug = second.debug = FALSE;
784     first.supportsNPS = second.supportsNPS = UNKNOWN;
785
786     /* [HGM] options */
787     first.optionSettings  = appData.firstOptions;
788     second.optionSettings = appData.secondOptions;
789
790     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
791     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
792     first.isUCI = appData.firstIsUCI; /* [AS] */
793     second.isUCI = appData.secondIsUCI; /* [AS] */
794     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
795     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
796
797     if (appData.firstProtocolVersion > PROTOVER ||
798         appData.firstProtocolVersion < 1) {
799       char buf[MSG_SIZ];
800       sprintf(buf, _("protocol version %d not supported"),
801               appData.firstProtocolVersion);
802       DisplayFatalError(buf, 0, 2);
803     } else {
804       first.protocolVersion = appData.firstProtocolVersion;
805     }
806
807     if (appData.secondProtocolVersion > PROTOVER ||
808         appData.secondProtocolVersion < 1) {
809       char buf[MSG_SIZ];
810       sprintf(buf, _("protocol version %d not supported"),
811               appData.secondProtocolVersion);
812       DisplayFatalError(buf, 0, 2);
813     } else {
814       second.protocolVersion = appData.secondProtocolVersion;
815     }
816
817     if (appData.icsActive) {
818         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
819 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
820     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
821         appData.clockMode = FALSE;
822         first.sendTime = second.sendTime = 0;
823     }
824
825 #if ZIPPY
826     /* Override some settings from environment variables, for backward
827        compatibility.  Unfortunately it's not feasible to have the env
828        vars just set defaults, at least in xboard.  Ugh.
829     */
830     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
831       ZippyInit();
832     }
833 #endif
834
835     if (appData.noChessProgram) {
836         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
837         sprintf(programVersion, "%s", PACKAGE_STRING);
838     } else {
839       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
840       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
841       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
842     }
843
844     if (!appData.icsActive) {
845       char buf[MSG_SIZ];
846       /* Check for variants that are supported only in ICS mode,
847          or not at all.  Some that are accepted here nevertheless
848          have bugs; see comments below.
849       */
850       VariantClass variant = StringToVariant(appData.variant);
851       switch (variant) {
852       case VariantBughouse:     /* need four players and two boards */
853       case VariantKriegspiel:   /* need to hide pieces and move details */
854       /* case VariantFischeRandom: (Fabien: moved below) */
855         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
856         DisplayFatalError(buf, 0, 2);
857         return;
858
859       case VariantUnknown:
860       case VariantLoadable:
861       case Variant29:
862       case Variant30:
863       case Variant31:
864       case Variant32:
865       case Variant33:
866       case Variant34:
867       case Variant35:
868       case Variant36:
869       default:
870         sprintf(buf, _("Unknown variant name %s"), appData.variant);
871         DisplayFatalError(buf, 0, 2);
872         return;
873
874       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
875       case VariantFairy:      /* [HGM] TestLegality definitely off! */
876       case VariantGothic:     /* [HGM] should work */
877       case VariantCapablanca: /* [HGM] should work */
878       case VariantCourier:    /* [HGM] initial forced moves not implemented */
879       case VariantShogi:      /* [HGM] drops not tested for legality */
880       case VariantKnightmate: /* [HGM] should work */
881       case VariantCylinder:   /* [HGM] untested */
882       case VariantFalcon:     /* [HGM] untested */
883       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
884                                  offboard interposition not understood */
885       case VariantNormal:     /* definitely works! */
886       case VariantWildCastle: /* pieces not automatically shuffled */
887       case VariantNoCastle:   /* pieces not automatically shuffled */
888       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
889       case VariantLosers:     /* should work except for win condition,
890                                  and doesn't know captures are mandatory */
891       case VariantSuicide:    /* should work except for win condition,
892                                  and doesn't know captures are mandatory */
893       case VariantGiveaway:   /* should work except for win condition,
894                                  and doesn't know captures are mandatory */
895       case VariantTwoKings:   /* should work */
896       case VariantAtomic:     /* should work except for win condition */
897       case Variant3Check:     /* should work except for win condition */
898       case VariantShatranj:   /* should work except for all win conditions */
899       case VariantMakruk:     /* should work except for daw countdown */
900       case VariantBerolina:   /* might work if TestLegality is off */
901       case VariantCapaRandom: /* should work */
902       case VariantJanus:      /* should work */
903       case VariantSuper:      /* experimental */
904       case VariantGreat:      /* experimental, requires legality testing to be off */
905         break;
906       }
907     }
908
909     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
910     InitEngineUCI( installDir, &second );
911 }
912
913 int NextIntegerFromString( char ** str, long * value )
914 {
915     int result = -1;
916     char * s = *str;
917
918     while( *s == ' ' || *s == '\t' ) {
919         s++;
920     }
921
922     *value = 0;
923
924     if( *s >= '0' && *s <= '9' ) {
925         while( *s >= '0' && *s <= '9' ) {
926             *value = *value * 10 + (*s - '0');
927             s++;
928         }
929
930         result = 0;
931     }
932
933     *str = s;
934
935     return result;
936 }
937
938 int NextTimeControlFromString( char ** str, long * value )
939 {
940     long temp;
941     int result = NextIntegerFromString( str, &temp );
942
943     if( result == 0 ) {
944         *value = temp * 60; /* Minutes */
945         if( **str == ':' ) {
946             (*str)++;
947             result = NextIntegerFromString( str, &temp );
948             *value += temp; /* Seconds */
949         }
950     }
951
952     return result;
953 }
954
955 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
956 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
957     int result = -1; long temp, temp2;
958
959     if(**str != '+') return -1; // old params remain in force!
960     (*str)++;
961     if( NextTimeControlFromString( str, &temp ) ) return -1;
962
963     if(**str != '/') {
964         /* time only: incremental or sudden-death time control */
965         if(**str == '+') { /* increment follows; read it */
966             (*str)++;
967             if(result = NextIntegerFromString( str, &temp2)) return -1;
968             *inc = temp2 * 1000;
969         } else *inc = 0;
970         *moves = 0; *tc = temp * 1000;
971         return 0;
972     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
973
974     (*str)++; /* classical time control */
975     result = NextTimeControlFromString( str, &temp2);
976     if(result == 0) {
977         *moves = temp/60;
978         *tc    = temp2 * 1000;
979         *inc   = 0;
980     }
981     return result;
982 }
983
984 int GetTimeQuota(int movenr)
985 {   /* [HGM] get time to add from the multi-session time-control string */
986     int moves=1; /* kludge to force reading of first session */
987     long time, increment;
988     char *s = fullTimeControlString;
989
990     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
991     do {
992         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
993         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
994         if(movenr == -1) return time;    /* last move before new session     */
995         if(!moves) return increment;     /* current session is incremental   */
996         if(movenr >= 0) movenr -= moves; /* we already finished this session */
997     } while(movenr >= -1);               /* try again for next session       */
998
999     return 0; // no new time quota on this move
1000 }
1001
1002 int
1003 ParseTimeControl(tc, ti, mps)
1004      char *tc;
1005      int ti;
1006      int mps;
1007 {
1008   long tc1;
1009   long tc2;
1010   char buf[MSG_SIZ];
1011   
1012   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1013   if(ti > 0) {
1014     if(mps)
1015       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1016     else sprintf(buf, "+%s+%d", tc, ti);
1017   } else {
1018     if(mps)
1019              sprintf(buf, "+%d/%s", mps, tc);
1020     else sprintf(buf, "+%s", tc);
1021   }
1022   fullTimeControlString = StrSave(buf);
1023   
1024   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1025     return FALSE;
1026   }
1027   
1028   if( *tc == '/' ) {
1029     /* Parse second time control */
1030     tc++;
1031     
1032     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1033       return FALSE;
1034     }
1035     
1036     if( tc2 == 0 ) {
1037       return FALSE;
1038     }
1039     
1040     timeControl_2 = tc2 * 1000;
1041   }
1042   else {
1043     timeControl_2 = 0;
1044   }
1045   
1046   if( tc1 == 0 ) {
1047     return FALSE;
1048   }
1049   
1050   timeControl = tc1 * 1000;
1051   
1052   if (ti >= 0) {
1053     timeIncrement = ti * 1000;  /* convert to ms */
1054     movesPerSession = 0;
1055   } else {
1056     timeIncrement = 0;
1057     movesPerSession = mps;
1058   }
1059   return TRUE;
1060 }
1061
1062 void
1063 InitBackEnd2()
1064 {
1065   if (appData.debugMode) {
1066     fprintf(debugFP, "%s\n", programVersion);
1067   }
1068
1069   set_cont_sequence(appData.wrapContSeq);
1070   if (appData.matchGames > 0) {
1071     appData.matchMode = TRUE;
1072   } else if (appData.matchMode) {
1073     appData.matchGames = 1;
1074   }
1075   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1076     appData.matchGames = appData.sameColorGames;
1077   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1078     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1079     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1080   }
1081   Reset(TRUE, FALSE);
1082   if (appData.noChessProgram || first.protocolVersion == 1) {
1083     InitBackEnd3();
1084     } else {
1085     /* kludge: allow timeout for initial "feature" commands */
1086     FreezeUI();
1087     DisplayMessage("", _("Starting chess program"));
1088     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1089   }
1090 }
1091
1092 void
1093 InitBackEnd3 P((void))
1094 {
1095     GameMode initialMode;
1096     char buf[MSG_SIZ];
1097     int err;
1098
1099     InitChessProgram(&first, startedFromSetupPosition);
1100
1101
1102     if (appData.icsActive) {
1103 #ifdef WIN32
1104         /* [DM] Make a console window if needed [HGM] merged ifs */
1105         ConsoleCreate();
1106 #endif
1107         err = establish();
1108         if (err != 0) {
1109             if (*appData.icsCommPort != NULLCHAR) {
1110                 sprintf(buf, _("Could not open comm port %s"),
1111                         appData.icsCommPort);
1112             } else {
1113                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1114                         appData.icsHost, appData.icsPort);
1115             }
1116             DisplayFatalError(buf, err, 1);
1117             return;
1118         }
1119         SetICSMode();
1120         telnetISR =
1121           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1122         fromUserISR =
1123           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1124     } else if (appData.noChessProgram) {
1125         SetNCPMode();
1126     } else {
1127         SetGNUMode();
1128     }
1129
1130     if (*appData.cmailGameName != NULLCHAR) {
1131         SetCmailMode();
1132         OpenLoopback(&cmailPR);
1133         cmailISR =
1134           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1135     }
1136
1137     ThawUI();
1138     DisplayMessage("", "");
1139     if (StrCaseCmp(appData.initialMode, "") == 0) {
1140       initialMode = BeginningOfGame;
1141     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1142       initialMode = TwoMachinesPlay;
1143     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1144       initialMode = AnalyzeFile;
1145     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1146       initialMode = AnalyzeMode;
1147     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1148       initialMode = MachinePlaysWhite;
1149     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1150       initialMode = MachinePlaysBlack;
1151     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1152       initialMode = EditGame;
1153     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1154       initialMode = EditPosition;
1155     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1156       initialMode = Training;
1157     } else {
1158       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1159       DisplayFatalError(buf, 0, 2);
1160       return;
1161     }
1162
1163     if (appData.matchMode) {
1164         /* Set up machine vs. machine match */
1165         if (appData.noChessProgram) {
1166             DisplayFatalError(_("Can't have a match with no chess programs"),
1167                               0, 2);
1168             return;
1169         }
1170         matchMode = TRUE;
1171         matchGame = 1;
1172         if (*appData.loadGameFile != NULLCHAR) {
1173             int index = appData.loadGameIndex; // [HGM] autoinc
1174             if(index<0) lastIndex = index = 1;
1175             if (!LoadGameFromFile(appData.loadGameFile,
1176                                   index,
1177                                   appData.loadGameFile, FALSE)) {
1178                 DisplayFatalError(_("Bad game file"), 0, 1);
1179                 return;
1180             }
1181         } else if (*appData.loadPositionFile != NULLCHAR) {
1182             int index = appData.loadPositionIndex; // [HGM] autoinc
1183             if(index<0) lastIndex = index = 1;
1184             if (!LoadPositionFromFile(appData.loadPositionFile,
1185                                       index,
1186                                       appData.loadPositionFile)) {
1187                 DisplayFatalError(_("Bad position file"), 0, 1);
1188                 return;
1189             }
1190         }
1191         TwoMachinesEvent();
1192     } else if (*appData.cmailGameName != NULLCHAR) {
1193         /* Set up cmail mode */
1194         ReloadCmailMsgEvent(TRUE);
1195     } else {
1196         /* Set up other modes */
1197         if (initialMode == AnalyzeFile) {
1198           if (*appData.loadGameFile == NULLCHAR) {
1199             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1200             return;
1201           }
1202         }
1203         if (*appData.loadGameFile != NULLCHAR) {
1204             (void) LoadGameFromFile(appData.loadGameFile,
1205                                     appData.loadGameIndex,
1206                                     appData.loadGameFile, TRUE);
1207         } else if (*appData.loadPositionFile != NULLCHAR) {
1208             (void) LoadPositionFromFile(appData.loadPositionFile,
1209                                         appData.loadPositionIndex,
1210                                         appData.loadPositionFile);
1211             /* [HGM] try to make self-starting even after FEN load */
1212             /* to allow automatic setup of fairy variants with wtm */
1213             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1214                 gameMode = BeginningOfGame;
1215                 setboardSpoiledMachineBlack = 1;
1216             }
1217             /* [HGM] loadPos: make that every new game uses the setup */
1218             /* from file as long as we do not switch variant          */
1219             if(!blackPlaysFirst) {
1220                 startedFromPositionFile = TRUE;
1221                 CopyBoard(filePosition, boards[0]);
1222             }
1223         }
1224         if (initialMode == AnalyzeMode) {
1225           if (appData.noChessProgram) {
1226             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1227             return;
1228           }
1229           if (appData.icsActive) {
1230             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1231             return;
1232           }
1233           AnalyzeModeEvent();
1234         } else if (initialMode == AnalyzeFile) {
1235           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1236           ShowThinkingEvent();
1237           AnalyzeFileEvent();
1238           AnalysisPeriodicEvent(1);
1239         } else if (initialMode == MachinePlaysWhite) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1242                               0, 2);
1243             return;
1244           }
1245           if (appData.icsActive) {
1246             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1247                               0, 2);
1248             return;
1249           }
1250           MachineWhiteEvent();
1251         } else if (initialMode == MachinePlaysBlack) {
1252           if (appData.noChessProgram) {
1253             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1254                               0, 2);
1255             return;
1256           }
1257           if (appData.icsActive) {
1258             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1259                               0, 2);
1260             return;
1261           }
1262           MachineBlackEvent();
1263         } else if (initialMode == TwoMachinesPlay) {
1264           if (appData.noChessProgram) {
1265             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1266                               0, 2);
1267             return;
1268           }
1269           if (appData.icsActive) {
1270             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1271                               0, 2);
1272             return;
1273           }
1274           TwoMachinesEvent();
1275         } else if (initialMode == EditGame) {
1276           EditGameEvent();
1277         } else if (initialMode == EditPosition) {
1278           EditPositionEvent();
1279         } else if (initialMode == Training) {
1280           if (*appData.loadGameFile == NULLCHAR) {
1281             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1282             return;
1283           }
1284           TrainingEvent();
1285         }
1286     }
1287 }
1288
1289 /*
1290  * Establish will establish a contact to a remote host.port.
1291  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1292  *  used to talk to the host.
1293  * Returns 0 if okay, error code if not.
1294  */
1295 int
1296 establish()
1297 {
1298     char buf[MSG_SIZ];
1299
1300     if (*appData.icsCommPort != NULLCHAR) {
1301         /* Talk to the host through a serial comm port */
1302         return OpenCommPort(appData.icsCommPort, &icsPR);
1303
1304     } else if (*appData.gateway != NULLCHAR) {
1305         if (*appData.remoteShell == NULLCHAR) {
1306             /* Use the rcmd protocol to run telnet program on a gateway host */
1307             snprintf(buf, sizeof(buf), "%s %s %s",
1308                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1309             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1310
1311         } else {
1312             /* Use the rsh program to run telnet program on a gateway host */
1313             if (*appData.remoteUser == NULLCHAR) {
1314                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1315                         appData.gateway, appData.telnetProgram,
1316                         appData.icsHost, appData.icsPort);
1317             } else {
1318                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1319                         appData.remoteShell, appData.gateway,
1320                         appData.remoteUser, appData.telnetProgram,
1321                         appData.icsHost, appData.icsPort);
1322             }
1323             return StartChildProcess(buf, "", &icsPR);
1324
1325         }
1326     } else if (appData.useTelnet) {
1327         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1328
1329     } else {
1330         /* TCP socket interface differs somewhat between
1331            Unix and NT; handle details in the front end.
1332            */
1333         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1334     }
1335 }
1336
1337 void
1338 show_bytes(fp, buf, count)
1339      FILE *fp;
1340      char *buf;
1341      int count;
1342 {
1343     while (count--) {
1344         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1345             fprintf(fp, "\\%03o", *buf & 0xff);
1346         } else {
1347             putc(*buf, fp);
1348         }
1349         buf++;
1350     }
1351     fflush(fp);
1352 }
1353
1354 /* Returns an errno value */
1355 int
1356 OutputMaybeTelnet(pr, message, count, outError)
1357      ProcRef pr;
1358      char *message;
1359      int count;
1360      int *outError;
1361 {
1362     char buf[8192], *p, *q, *buflim;
1363     int left, newcount, outcount;
1364
1365     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1366         *appData.gateway != NULLCHAR) {
1367         if (appData.debugMode) {
1368             fprintf(debugFP, ">ICS: ");
1369             show_bytes(debugFP, message, count);
1370             fprintf(debugFP, "\n");
1371         }
1372         return OutputToProcess(pr, message, count, outError);
1373     }
1374
1375     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1376     p = message;
1377     q = buf;
1378     left = count;
1379     newcount = 0;
1380     while (left) {
1381         if (q >= buflim) {
1382             if (appData.debugMode) {
1383                 fprintf(debugFP, ">ICS: ");
1384                 show_bytes(debugFP, buf, newcount);
1385                 fprintf(debugFP, "\n");
1386             }
1387             outcount = OutputToProcess(pr, buf, newcount, outError);
1388             if (outcount < newcount) return -1; /* to be sure */
1389             q = buf;
1390             newcount = 0;
1391         }
1392         if (*p == '\n') {
1393             *q++ = '\r';
1394             newcount++;
1395         } else if (((unsigned char) *p) == TN_IAC) {
1396             *q++ = (char) TN_IAC;
1397             newcount ++;
1398         }
1399         *q++ = *p++;
1400         newcount++;
1401         left--;
1402     }
1403     if (appData.debugMode) {
1404         fprintf(debugFP, ">ICS: ");
1405         show_bytes(debugFP, buf, newcount);
1406         fprintf(debugFP, "\n");
1407     }
1408     outcount = OutputToProcess(pr, buf, newcount, outError);
1409     if (outcount < newcount) return -1; /* to be sure */
1410     return count;
1411 }
1412
1413 void
1414 read_from_player(isr, closure, message, count, error)
1415      InputSourceRef isr;
1416      VOIDSTAR closure;
1417      char *message;
1418      int count;
1419      int error;
1420 {
1421     int outError, outCount;
1422     static int gotEof = 0;
1423
1424     /* Pass data read from player on to ICS */
1425     if (count > 0) {
1426         gotEof = 0;
1427         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1428         if (outCount < count) {
1429             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1430         }
1431     } else if (count < 0) {
1432         RemoveInputSource(isr);
1433         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1434     } else if (gotEof++ > 0) {
1435         RemoveInputSource(isr);
1436         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1437     }
1438 }
1439
1440 void
1441 KeepAlive()
1442 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1443     SendToICS("date\n");
1444     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1445 }
1446
1447 /* added routine for printf style output to ics */
1448 void ics_printf(char *format, ...)
1449 {
1450     char buffer[MSG_SIZ];
1451     va_list args;
1452
1453     va_start(args, format);
1454     vsnprintf(buffer, sizeof(buffer), format, args);
1455     buffer[sizeof(buffer)-1] = '\0';
1456     SendToICS(buffer);
1457     va_end(args);
1458 }
1459
1460 void
1461 SendToICS(s)
1462      char *s;
1463 {
1464     int count, outCount, outError;
1465
1466     if (icsPR == NULL) return;
1467
1468     count = strlen(s);
1469     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1470     if (outCount < count) {
1471         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1472     }
1473 }
1474
1475 /* This is used for sending logon scripts to the ICS. Sending
1476    without a delay causes problems when using timestamp on ICC
1477    (at least on my machine). */
1478 void
1479 SendToICSDelayed(s,msdelay)
1480      char *s;
1481      long msdelay;
1482 {
1483     int count, outCount, outError;
1484
1485     if (icsPR == NULL) return;
1486
1487     count = strlen(s);
1488     if (appData.debugMode) {
1489         fprintf(debugFP, ">ICS: ");
1490         show_bytes(debugFP, s, count);
1491         fprintf(debugFP, "\n");
1492     }
1493     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1494                                       msdelay);
1495     if (outCount < count) {
1496         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1497     }
1498 }
1499
1500
1501 /* Remove all highlighting escape sequences in s
1502    Also deletes any suffix starting with '('
1503    */
1504 char *
1505 StripHighlightAndTitle(s)
1506      char *s;
1507 {
1508     static char retbuf[MSG_SIZ];
1509     char *p = retbuf;
1510
1511     while (*s != NULLCHAR) {
1512         while (*s == '\033') {
1513             while (*s != NULLCHAR && !isalpha(*s)) s++;
1514             if (*s != NULLCHAR) s++;
1515         }
1516         while (*s != NULLCHAR && *s != '\033') {
1517             if (*s == '(' || *s == '[') {
1518                 *p = NULLCHAR;
1519                 return retbuf;
1520             }
1521             *p++ = *s++;
1522         }
1523     }
1524     *p = NULLCHAR;
1525     return retbuf;
1526 }
1527
1528 /* Remove all highlighting escape sequences in s */
1529 char *
1530 StripHighlight(s)
1531      char *s;
1532 {
1533     static char retbuf[MSG_SIZ];
1534     char *p = retbuf;
1535
1536     while (*s != NULLCHAR) {
1537         while (*s == '\033') {
1538             while (*s != NULLCHAR && !isalpha(*s)) s++;
1539             if (*s != NULLCHAR) s++;
1540         }
1541         while (*s != NULLCHAR && *s != '\033') {
1542             *p++ = *s++;
1543         }
1544     }
1545     *p = NULLCHAR;
1546     return retbuf;
1547 }
1548
1549 char *variantNames[] = VARIANT_NAMES;
1550 char *
1551 VariantName(v)
1552      VariantClass v;
1553 {
1554     return variantNames[v];
1555 }
1556
1557
1558 /* Identify a variant from the strings the chess servers use or the
1559    PGN Variant tag names we use. */
1560 VariantClass
1561 StringToVariant(e)
1562      char *e;
1563 {
1564     char *p;
1565     int wnum = -1;
1566     VariantClass v = VariantNormal;
1567     int i, found = FALSE;
1568     char buf[MSG_SIZ];
1569
1570     if (!e) return v;
1571
1572     /* [HGM] skip over optional board-size prefixes */
1573     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1574         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1575         while( *e++ != '_');
1576     }
1577
1578     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1579         v = VariantNormal;
1580         found = TRUE;
1581     } else
1582     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1583       if (StrCaseStr(e, variantNames[i])) {
1584         v = (VariantClass) i;
1585         found = TRUE;
1586         break;
1587       }
1588     }
1589
1590     if (!found) {
1591       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1592           || StrCaseStr(e, "wild/fr")
1593           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1594         v = VariantFischeRandom;
1595       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1596                  (i = 1, p = StrCaseStr(e, "w"))) {
1597         p += i;
1598         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1599         if (isdigit(*p)) {
1600           wnum = atoi(p);
1601         } else {
1602           wnum = -1;
1603         }
1604         switch (wnum) {
1605         case 0: /* FICS only, actually */
1606         case 1:
1607           /* Castling legal even if K starts on d-file */
1608           v = VariantWildCastle;
1609           break;
1610         case 2:
1611         case 3:
1612         case 4:
1613           /* Castling illegal even if K & R happen to start in
1614              normal positions. */
1615           v = VariantNoCastle;
1616           break;
1617         case 5:
1618         case 7:
1619         case 8:
1620         case 10:
1621         case 11:
1622         case 12:
1623         case 13:
1624         case 14:
1625         case 15:
1626         case 18:
1627         case 19:
1628           /* Castling legal iff K & R start in normal positions */
1629           v = VariantNormal;
1630           break;
1631         case 6:
1632         case 20:
1633         case 21:
1634           /* Special wilds for position setup; unclear what to do here */
1635           v = VariantLoadable;
1636           break;
1637         case 9:
1638           /* Bizarre ICC game */
1639           v = VariantTwoKings;
1640           break;
1641         case 16:
1642           v = VariantKriegspiel;
1643           break;
1644         case 17:
1645           v = VariantLosers;
1646           break;
1647         case 22:
1648           v = VariantFischeRandom;
1649           break;
1650         case 23:
1651           v = VariantCrazyhouse;
1652           break;
1653         case 24:
1654           v = VariantBughouse;
1655           break;
1656         case 25:
1657           v = Variant3Check;
1658           break;
1659         case 26:
1660           /* Not quite the same as FICS suicide! */
1661           v = VariantGiveaway;
1662           break;
1663         case 27:
1664           v = VariantAtomic;
1665           break;
1666         case 28:
1667           v = VariantShatranj;
1668           break;
1669
1670         /* Temporary names for future ICC types.  The name *will* change in
1671            the next xboard/WinBoard release after ICC defines it. */
1672         case 29:
1673           v = Variant29;
1674           break;
1675         case 30:
1676           v = Variant30;
1677           break;
1678         case 31:
1679           v = Variant31;
1680           break;
1681         case 32:
1682           v = Variant32;
1683           break;
1684         case 33:
1685           v = Variant33;
1686           break;
1687         case 34:
1688           v = Variant34;
1689           break;
1690         case 35:
1691           v = Variant35;
1692           break;
1693         case 36:
1694           v = Variant36;
1695           break;
1696         case 37:
1697           v = VariantShogi;
1698           break;
1699         case 38:
1700           v = VariantXiangqi;
1701           break;
1702         case 39:
1703           v = VariantCourier;
1704           break;
1705         case 40:
1706           v = VariantGothic;
1707           break;
1708         case 41:
1709           v = VariantCapablanca;
1710           break;
1711         case 42:
1712           v = VariantKnightmate;
1713           break;
1714         case 43:
1715           v = VariantFairy;
1716           break;
1717         case 44:
1718           v = VariantCylinder;
1719           break;
1720         case 45:
1721           v = VariantFalcon;
1722           break;
1723         case 46:
1724           v = VariantCapaRandom;
1725           break;
1726         case 47:
1727           v = VariantBerolina;
1728           break;
1729         case 48:
1730           v = VariantJanus;
1731           break;
1732         case 49:
1733           v = VariantSuper;
1734           break;
1735         case 50:
1736           v = VariantGreat;
1737           break;
1738         case -1:
1739           /* Found "wild" or "w" in the string but no number;
1740              must assume it's normal chess. */
1741           v = VariantNormal;
1742           break;
1743         default:
1744           sprintf(buf, _("Unknown wild type %d"), wnum);
1745           DisplayError(buf, 0);
1746           v = VariantUnknown;
1747           break;
1748         }
1749       }
1750     }
1751     if (appData.debugMode) {
1752       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1753               e, wnum, VariantName(v));
1754     }
1755     return v;
1756 }
1757
1758 static int leftover_start = 0, leftover_len = 0;
1759 char star_match[STAR_MATCH_N][MSG_SIZ];
1760
1761 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1762    advance *index beyond it, and set leftover_start to the new value of
1763    *index; else return FALSE.  If pattern contains the character '*', it
1764    matches any sequence of characters not containing '\r', '\n', or the
1765    character following the '*' (if any), and the matched sequence(s) are
1766    copied into star_match.
1767    */
1768 int
1769 looking_at(buf, index, pattern)
1770      char *buf;
1771      int *index;
1772      char *pattern;
1773 {
1774     char *bufp = &buf[*index], *patternp = pattern;
1775     int star_count = 0;
1776     char *matchp = star_match[0];
1777
1778     for (;;) {
1779         if (*patternp == NULLCHAR) {
1780             *index = leftover_start = bufp - buf;
1781             *matchp = NULLCHAR;
1782             return TRUE;
1783         }
1784         if (*bufp == NULLCHAR) return FALSE;
1785         if (*patternp == '*') {
1786             if (*bufp == *(patternp + 1)) {
1787                 *matchp = NULLCHAR;
1788                 matchp = star_match[++star_count];
1789                 patternp += 2;
1790                 bufp++;
1791                 continue;
1792             } else if (*bufp == '\n' || *bufp == '\r') {
1793                 patternp++;
1794                 if (*patternp == NULLCHAR)
1795                   continue;
1796                 else
1797                   return FALSE;
1798             } else {
1799                 *matchp++ = *bufp++;
1800                 continue;
1801             }
1802         }
1803         if (*patternp != *bufp) return FALSE;
1804         patternp++;
1805         bufp++;
1806     }
1807 }
1808
1809 void
1810 SendToPlayer(data, length)
1811      char *data;
1812      int length;
1813 {
1814     int error, outCount;
1815     outCount = OutputToProcess(NoProc, data, length, &error);
1816     if (outCount < length) {
1817         DisplayFatalError(_("Error writing to display"), error, 1);
1818     }
1819 }
1820
1821 void
1822 PackHolding(packed, holding)
1823      char packed[];
1824      char *holding;
1825 {
1826     char *p = holding;
1827     char *q = packed;
1828     int runlength = 0;
1829     int curr = 9999;
1830     do {
1831         if (*p == curr) {
1832             runlength++;
1833         } else {
1834             switch (runlength) {
1835               case 0:
1836                 break;
1837               case 1:
1838                 *q++ = curr;
1839                 break;
1840               case 2:
1841                 *q++ = curr;
1842                 *q++ = curr;
1843                 break;
1844               default:
1845                 sprintf(q, "%d", runlength);
1846                 while (*q) q++;
1847                 *q++ = curr;
1848                 break;
1849             }
1850             runlength = 1;
1851             curr = *p;
1852         }
1853     } while (*p++);
1854     *q = NULLCHAR;
1855 }
1856
1857 /* Telnet protocol requests from the front end */
1858 void
1859 TelnetRequest(ddww, option)
1860      unsigned char ddww, option;
1861 {
1862     unsigned char msg[3];
1863     int outCount, outError;
1864
1865     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1866
1867     if (appData.debugMode) {
1868         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1869         switch (ddww) {
1870           case TN_DO:
1871             ddwwStr = "DO";
1872             break;
1873           case TN_DONT:
1874             ddwwStr = "DONT";
1875             break;
1876           case TN_WILL:
1877             ddwwStr = "WILL";
1878             break;
1879           case TN_WONT:
1880             ddwwStr = "WONT";
1881             break;
1882           default:
1883             ddwwStr = buf1;
1884             sprintf(buf1, "%d", ddww);
1885             break;
1886         }
1887         switch (option) {
1888           case TN_ECHO:
1889             optionStr = "ECHO";
1890             break;
1891           default:
1892             optionStr = buf2;
1893             sprintf(buf2, "%d", option);
1894             break;
1895         }
1896         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1897     }
1898     msg[0] = TN_IAC;
1899     msg[1] = ddww;
1900     msg[2] = option;
1901     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1902     if (outCount < 3) {
1903         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1904     }
1905 }
1906
1907 void
1908 DoEcho()
1909 {
1910     if (!appData.icsActive) return;
1911     TelnetRequest(TN_DO, TN_ECHO);
1912 }
1913
1914 void
1915 DontEcho()
1916 {
1917     if (!appData.icsActive) return;
1918     TelnetRequest(TN_DONT, TN_ECHO);
1919 }
1920
1921 void
1922 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1923 {
1924     /* put the holdings sent to us by the server on the board holdings area */
1925     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1926     char p;
1927     ChessSquare piece;
1928
1929     if(gameInfo.holdingsWidth < 2)  return;
1930     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1931         return; // prevent overwriting by pre-board holdings
1932
1933     if( (int)lowestPiece >= BlackPawn ) {
1934         holdingsColumn = 0;
1935         countsColumn = 1;
1936         holdingsStartRow = BOARD_HEIGHT-1;
1937         direction = -1;
1938     } else {
1939         holdingsColumn = BOARD_WIDTH-1;
1940         countsColumn = BOARD_WIDTH-2;
1941         holdingsStartRow = 0;
1942         direction = 1;
1943     }
1944
1945     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1946         board[i][holdingsColumn] = EmptySquare;
1947         board[i][countsColumn]   = (ChessSquare) 0;
1948     }
1949     while( (p=*holdings++) != NULLCHAR ) {
1950         piece = CharToPiece( ToUpper(p) );
1951         if(piece == EmptySquare) continue;
1952         /*j = (int) piece - (int) WhitePawn;*/
1953         j = PieceToNumber(piece);
1954         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1955         if(j < 0) continue;               /* should not happen */
1956         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1957         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1958         board[holdingsStartRow+j*direction][countsColumn]++;
1959     }
1960 }
1961
1962
1963 void
1964 VariantSwitch(Board board, VariantClass newVariant)
1965 {
1966    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1967    Board oldBoard;
1968
1969    startedFromPositionFile = FALSE;
1970    if(gameInfo.variant == newVariant) return;
1971
1972    /* [HGM] This routine is called each time an assignment is made to
1973     * gameInfo.variant during a game, to make sure the board sizes
1974     * are set to match the new variant. If that means adding or deleting
1975     * holdings, we shift the playing board accordingly
1976     * This kludge is needed because in ICS observe mode, we get boards
1977     * of an ongoing game without knowing the variant, and learn about the
1978     * latter only later. This can be because of the move list we requested,
1979     * in which case the game history is refilled from the beginning anyway,
1980     * but also when receiving holdings of a crazyhouse game. In the latter
1981     * case we want to add those holdings to the already received position.
1982     */
1983    
1984    if (appData.debugMode) {
1985      fprintf(debugFP, "Switch board from %s to %s\n",
1986              VariantName(gameInfo.variant), VariantName(newVariant));
1987      setbuf(debugFP, NULL);
1988    }
1989    shuffleOpenings = 0;       /* [HGM] shuffle */
1990    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1991    switch(newVariant) 
1992      {
1993      case VariantShogi:
1994        newWidth = 9;  newHeight = 9;
1995        gameInfo.holdingsSize = 7;
1996      case VariantBughouse:
1997      case VariantCrazyhouse:
1998        newHoldingsWidth = 2; break;
1999      case VariantGreat:
2000        newWidth = 10;
2001      case VariantSuper:
2002        newHoldingsWidth = 2;
2003        gameInfo.holdingsSize = 8;
2004        break;
2005      case VariantGothic:
2006      case VariantCapablanca:
2007      case VariantCapaRandom:
2008        newWidth = 10;
2009      default:
2010        newHoldingsWidth = gameInfo.holdingsSize = 0;
2011      };
2012    
2013    if(newWidth  != gameInfo.boardWidth  ||
2014       newHeight != gameInfo.boardHeight ||
2015       newHoldingsWidth != gameInfo.holdingsWidth ) {
2016      
2017      /* shift position to new playing area, if needed */
2018      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2019        for(i=0; i<BOARD_HEIGHT; i++) 
2020          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2021            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2022              board[i][j];
2023        for(i=0; i<newHeight; i++) {
2024          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2025          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2026        }
2027      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2028        for(i=0; i<BOARD_HEIGHT; i++)
2029          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2030            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2031              board[i][j];
2032      }
2033      gameInfo.boardWidth  = newWidth;
2034      gameInfo.boardHeight = newHeight;
2035      gameInfo.holdingsWidth = newHoldingsWidth;
2036      gameInfo.variant = newVariant;
2037      InitDrawingSizes(-2, 0);
2038    } else gameInfo.variant = newVariant;
2039    CopyBoard(oldBoard, board);   // remember correctly formatted board
2040      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2041    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2042 }
2043
2044 static int loggedOn = FALSE;
2045
2046 /*-- Game start info cache: --*/
2047 int gs_gamenum;
2048 char gs_kind[MSG_SIZ];
2049 static char player1Name[128] = "";
2050 static char player2Name[128] = "";
2051 static char cont_seq[] = "\n\\   ";
2052 static int player1Rating = -1;
2053 static int player2Rating = -1;
2054 /*----------------------------*/
2055
2056 ColorClass curColor = ColorNormal;
2057 int suppressKibitz = 0;
2058
2059 void
2060 read_from_ics(isr, closure, data, count, error)
2061      InputSourceRef isr;
2062      VOIDSTAR closure;
2063      char *data;
2064      int count;
2065      int error;
2066 {
2067 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2068 #define STARTED_NONE 0
2069 #define STARTED_MOVES 1
2070 #define STARTED_BOARD 2
2071 #define STARTED_OBSERVE 3
2072 #define STARTED_HOLDINGS 4
2073 #define STARTED_CHATTER 5
2074 #define STARTED_COMMENT 6
2075 #define STARTED_MOVES_NOHIDE 7
2076
2077     static int started = STARTED_NONE;
2078     static char parse[20000];
2079     static int parse_pos = 0;
2080     static char buf[BUF_SIZE + 1];
2081     static int firstTime = TRUE, intfSet = FALSE;
2082     static ColorClass prevColor = ColorNormal;
2083     static int savingComment = FALSE;
2084     static int cmatch = 0; // continuation sequence match
2085     char *bp;
2086     char str[500];
2087     int i, oldi;
2088     int buf_len;
2089     int next_out;
2090     int tkind;
2091     int backup;    /* [DM] For zippy color lines */
2092     char *p;
2093     char talker[MSG_SIZ]; // [HGM] chat
2094     int channel;
2095
2096     if (appData.debugMode) {
2097       if (!error) {
2098         fprintf(debugFP, "<ICS: ");
2099         show_bytes(debugFP, data, count);
2100         fprintf(debugFP, "\n");
2101       }
2102     }
2103
2104     if (appData.debugMode) { int f = forwardMostMove;
2105         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2106                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2107                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2108     }
2109     if (count > 0) {
2110         /* If last read ended with a partial line that we couldn't parse,
2111            prepend it to the new read and try again. */
2112         if (leftover_len > 0) {
2113             for (i=0; i<leftover_len; i++)
2114               buf[i] = buf[leftover_start + i];
2115         }
2116
2117     /* copy new characters into the buffer */
2118     bp = buf + leftover_len;
2119     buf_len=leftover_len;
2120     for (i=0; i<count; i++)
2121     {
2122         // ignore these
2123         if (data[i] == '\r')
2124             continue;
2125
2126         // join lines split by ICS?
2127         if (!appData.noJoin)
2128         {
2129             /*
2130                 Joining just consists of finding matches against the
2131                 continuation sequence, and discarding that sequence
2132                 if found instead of copying it.  So, until a match
2133                 fails, there's nothing to do since it might be the
2134                 complete sequence, and thus, something we don't want
2135                 copied.
2136             */
2137             if (data[i] == cont_seq[cmatch])
2138             {
2139                 cmatch++;
2140                 if (cmatch == strlen(cont_seq))
2141                 {
2142                     cmatch = 0; // complete match.  just reset the counter
2143
2144                     /*
2145                         it's possible for the ICS to not include the space
2146                         at the end of the last word, making our [correct]
2147                         join operation fuse two separate words.  the server
2148                         does this when the space occurs at the width setting.
2149                     */
2150                     if (!buf_len || buf[buf_len-1] != ' ')
2151                     {
2152                         *bp++ = ' ';
2153                         buf_len++;
2154                     }
2155                 }
2156                 continue;
2157             }
2158             else if (cmatch)
2159             {
2160                 /*
2161                     match failed, so we have to copy what matched before
2162                     falling through and copying this character.  In reality,
2163                     this will only ever be just the newline character, but
2164                     it doesn't hurt to be precise.
2165                 */
2166                 strncpy(bp, cont_seq, cmatch);
2167                 bp += cmatch;
2168                 buf_len += cmatch;
2169                 cmatch = 0;
2170             }
2171         }
2172
2173         // copy this char
2174         *bp++ = data[i];
2175         buf_len++;
2176     }
2177
2178         buf[buf_len] = NULLCHAR;
2179 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2180         next_out = 0;
2181         leftover_start = 0;
2182
2183         i = 0;
2184         while (i < buf_len) {
2185             /* Deal with part of the TELNET option negotiation
2186                protocol.  We refuse to do anything beyond the
2187                defaults, except that we allow the WILL ECHO option,
2188                which ICS uses to turn off password echoing when we are
2189                directly connected to it.  We reject this option
2190                if localLineEditing mode is on (always on in xboard)
2191                and we are talking to port 23, which might be a real
2192                telnet server that will try to keep WILL ECHO on permanently.
2193              */
2194             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2195                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2196                 unsigned char option;
2197                 oldi = i;
2198                 switch ((unsigned char) buf[++i]) {
2199                   case TN_WILL:
2200                     if (appData.debugMode)
2201                       fprintf(debugFP, "\n<WILL ");
2202                     switch (option = (unsigned char) buf[++i]) {
2203                       case TN_ECHO:
2204                         if (appData.debugMode)
2205                           fprintf(debugFP, "ECHO ");
2206                         /* Reply only if this is a change, according
2207                            to the protocol rules. */
2208                         if (remoteEchoOption) break;
2209                         if (appData.localLineEditing &&
2210                             atoi(appData.icsPort) == TN_PORT) {
2211                             TelnetRequest(TN_DONT, TN_ECHO);
2212                         } else {
2213                             EchoOff();
2214                             TelnetRequest(TN_DO, TN_ECHO);
2215                             remoteEchoOption = TRUE;
2216                         }
2217                         break;
2218                       default:
2219                         if (appData.debugMode)
2220                           fprintf(debugFP, "%d ", option);
2221                         /* Whatever this is, we don't want it. */
2222                         TelnetRequest(TN_DONT, option);
2223                         break;
2224                     }
2225                     break;
2226                   case TN_WONT:
2227                     if (appData.debugMode)
2228                       fprintf(debugFP, "\n<WONT ");
2229                     switch (option = (unsigned char) buf[++i]) {
2230                       case TN_ECHO:
2231                         if (appData.debugMode)
2232                           fprintf(debugFP, "ECHO ");
2233                         /* Reply only if this is a change, according
2234                            to the protocol rules. */
2235                         if (!remoteEchoOption) break;
2236                         EchoOn();
2237                         TelnetRequest(TN_DONT, TN_ECHO);
2238                         remoteEchoOption = FALSE;
2239                         break;
2240                       default:
2241                         if (appData.debugMode)
2242                           fprintf(debugFP, "%d ", (unsigned char) option);
2243                         /* Whatever this is, it must already be turned
2244                            off, because we never agree to turn on
2245                            anything non-default, so according to the
2246                            protocol rules, we don't reply. */
2247                         break;
2248                     }
2249                     break;
2250                   case TN_DO:
2251                     if (appData.debugMode)
2252                       fprintf(debugFP, "\n<DO ");
2253                     switch (option = (unsigned char) buf[++i]) {
2254                       default:
2255                         /* Whatever this is, we refuse to do it. */
2256                         if (appData.debugMode)
2257                           fprintf(debugFP, "%d ", option);
2258                         TelnetRequest(TN_WONT, option);
2259                         break;
2260                     }
2261                     break;
2262                   case TN_DONT:
2263                     if (appData.debugMode)
2264                       fprintf(debugFP, "\n<DONT ");
2265                     switch (option = (unsigned char) buf[++i]) {
2266                       default:
2267                         if (appData.debugMode)
2268                           fprintf(debugFP, "%d ", option);
2269                         /* Whatever this is, we are already not doing
2270                            it, because we never agree to do anything
2271                            non-default, so according to the protocol
2272                            rules, we don't reply. */
2273                         break;
2274                     }
2275                     break;
2276                   case TN_IAC:
2277                     if (appData.debugMode)
2278                       fprintf(debugFP, "\n<IAC ");
2279                     /* Doubled IAC; pass it through */
2280                     i--;
2281                     break;
2282                   default:
2283                     if (appData.debugMode)
2284                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2285                     /* Drop all other telnet commands on the floor */
2286                     break;
2287                 }
2288                 if (oldi > next_out)
2289                   SendToPlayer(&buf[next_out], oldi - next_out);
2290                 if (++i > next_out)
2291                   next_out = i;
2292                 continue;
2293             }
2294
2295             /* OK, this at least will *usually* work */
2296             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2297                 loggedOn = TRUE;
2298             }
2299
2300             if (loggedOn && !intfSet) {
2301                 if (ics_type == ICS_ICC) {
2302                   sprintf(str,
2303                           "/set-quietly interface %s\n/set-quietly style 12\n",
2304                           programVersion);
2305                 } else if (ics_type == ICS_CHESSNET) {
2306                   sprintf(str, "/style 12\n");
2307                 } else {
2308                   strcpy(str, "alias $ @\n$set interface ");
2309                   strcat(str, programVersion);
2310                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2311 #ifdef WIN32
2312                   strcat(str, "$iset nohighlight 1\n");
2313 #endif
2314                   strcat(str, "$iset lock 1\n$style 12\n");
2315                 }
2316                 SendToICS(str);
2317                 NotifyFrontendLogin();
2318                 intfSet = TRUE;
2319             }
2320
2321             if (started == STARTED_COMMENT) {
2322                 /* Accumulate characters in comment */
2323                 parse[parse_pos++] = buf[i];
2324                 if (buf[i] == '\n') {
2325                     parse[parse_pos] = NULLCHAR;
2326                     if(chattingPartner>=0) {
2327                         char mess[MSG_SIZ];
2328                         sprintf(mess, "%s%s", talker, parse);
2329                         OutputChatMessage(chattingPartner, mess);
2330                         chattingPartner = -1;
2331                     } else
2332                     if(!suppressKibitz) // [HGM] kibitz
2333                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2334                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2335                         int nrDigit = 0, nrAlph = 0, j;
2336                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2337                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2338                         parse[parse_pos] = NULLCHAR;
2339                         // try to be smart: if it does not look like search info, it should go to
2340                         // ICS interaction window after all, not to engine-output window.
2341                         for(j=0; j<parse_pos; j++) { // count letters and digits
2342                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2343                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2344                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2345                         }
2346                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2347                             int depth=0; float score;
2348                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2349                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2350                                 pvInfoList[forwardMostMove-1].depth = depth;
2351                                 pvInfoList[forwardMostMove-1].score = 100*score;
2352                             }
2353                             OutputKibitz(suppressKibitz, parse);
2354                             next_out = i+1; // [HGM] suppress printing in ICS window
2355                         } else {
2356                             char tmp[MSG_SIZ];
2357                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2358                             SendToPlayer(tmp, strlen(tmp));
2359                         }
2360                     }
2361                     started = STARTED_NONE;
2362                 } else {
2363                     /* Don't match patterns against characters in comment */
2364                     i++;
2365                     continue;
2366                 }
2367             }
2368             if (started == STARTED_CHATTER) {
2369                 if (buf[i] != '\n') {
2370                     /* Don't match patterns against characters in chatter */
2371                     i++;
2372                     continue;
2373                 }
2374                 started = STARTED_NONE;
2375             }
2376
2377             /* Kludge to deal with rcmd protocol */
2378             if (firstTime && looking_at(buf, &i, "\001*")) {
2379                 DisplayFatalError(&buf[1], 0, 1);
2380                 continue;
2381             } else {
2382                 firstTime = FALSE;
2383             }
2384
2385             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2386                 ics_type = ICS_ICC;
2387                 ics_prefix = "/";
2388                 if (appData.debugMode)
2389                   fprintf(debugFP, "ics_type %d\n", ics_type);
2390                 continue;
2391             }
2392             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2393                 ics_type = ICS_FICS;
2394                 ics_prefix = "$";
2395                 if (appData.debugMode)
2396                   fprintf(debugFP, "ics_type %d\n", ics_type);
2397                 continue;
2398             }
2399             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2400                 ics_type = ICS_CHESSNET;
2401                 ics_prefix = "/";
2402                 if (appData.debugMode)
2403                   fprintf(debugFP, "ics_type %d\n", ics_type);
2404                 continue;
2405             }
2406
2407             if (!loggedOn &&
2408                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2409                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2410                  looking_at(buf, &i, "will be \"*\""))) {
2411               strcpy(ics_handle, star_match[0]);
2412               continue;
2413             }
2414
2415             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2416               char buf[MSG_SIZ];
2417               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2418               DisplayIcsInteractionTitle(buf);
2419               have_set_title = TRUE;
2420             }
2421
2422             /* skip finger notes */
2423             if (started == STARTED_NONE &&
2424                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2425                  (buf[i] == '1' && buf[i+1] == '0')) &&
2426                 buf[i+2] == ':' && buf[i+3] == ' ') {
2427               started = STARTED_CHATTER;
2428               i += 3;
2429               continue;
2430             }
2431
2432             /* skip formula vars */
2433             if (started == STARTED_NONE &&
2434                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2435               started = STARTED_CHATTER;
2436               i += 3;
2437               continue;
2438             }
2439
2440             oldi = i;
2441             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2442             if (appData.autoKibitz && started == STARTED_NONE &&
2443                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2444                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2445                 if(looking_at(buf, &i, "* kibitzes: ") &&
2446                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2447                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2448                         suppressKibitz = TRUE;
2449                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2450                                 && (gameMode == IcsPlayingWhite)) ||
2451                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2452                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2453                             started = STARTED_CHATTER; // own kibitz we simply discard
2454                         else {
2455                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2456                             parse_pos = 0; parse[0] = NULLCHAR;
2457                             savingComment = TRUE;
2458                             suppressKibitz = gameMode != IcsObserving ? 2 :
2459                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2460                         }
2461                         continue;
2462                 } else
2463                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2464                     // suppress the acknowledgements of our own autoKibitz
2465                     SendToPlayer(star_match[0], strlen(star_match[0]));
2466                     looking_at(buf, &i, "*% "); // eat prompt
2467                     next_out = i;
2468                 }
2469             } // [HGM] kibitz: end of patch
2470
2471 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2472
2473             // [HGM] chat: intercept tells by users for which we have an open chat window
2474             channel = -1;
2475             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2476                                            looking_at(buf, &i, "* whispers:") ||
2477                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2478                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2479                 int p;
2480                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2481                 chattingPartner = -1;
2482
2483                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2484                 for(p=0; p<MAX_CHAT; p++) {
2485                     if(channel == atoi(chatPartner[p])) {
2486                     talker[0] = '['; strcat(talker, "]");
2487                     chattingPartner = p; break;
2488                     }
2489                 } else
2490                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2491                 for(p=0; p<MAX_CHAT; p++) {
2492                     if(!strcmp("WHISPER", chatPartner[p])) {
2493                         talker[0] = '['; strcat(talker, "]");
2494                         chattingPartner = p; break;
2495                     }
2496                 }
2497                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2498                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2499                     talker[0] = 0;
2500                     chattingPartner = p; break;
2501                 }
2502                 if(chattingPartner<0) i = oldi; else {
2503                     started = STARTED_COMMENT;
2504                     parse_pos = 0; parse[0] = NULLCHAR;
2505                     savingComment = TRUE;
2506                     suppressKibitz = TRUE;
2507                 }
2508             } // [HGM] chat: end of patch
2509
2510             if (appData.zippyTalk || appData.zippyPlay) {
2511                 /* [DM] Backup address for color zippy lines */
2512                 backup = i;
2513 #if ZIPPY
2514        #ifdef WIN32
2515                if (loggedOn == TRUE)
2516                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2517                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2518        #else
2519                 if (ZippyControl(buf, &i) ||
2520                     ZippyConverse(buf, &i) ||
2521                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2522                       loggedOn = TRUE;
2523                       if (!appData.colorize) continue;
2524                 }
2525        #endif
2526 #endif
2527             } // [DM] 'else { ' deleted
2528                 if (
2529                     /* Regular tells and says */
2530                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2531                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2532                     looking_at(buf, &i, "* says: ") ||
2533                     /* Don't color "message" or "messages" output */
2534                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2535                     looking_at(buf, &i, "*. * at *:*: ") ||
2536                     looking_at(buf, &i, "--* (*:*): ") ||
2537                     /* Message notifications (same color as tells) */
2538                     looking_at(buf, &i, "* has left a message ") ||
2539                     looking_at(buf, &i, "* just sent you a message:\n") ||
2540                     /* Whispers and kibitzes */
2541                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2542                     looking_at(buf, &i, "* kibitzes: ") ||
2543                     /* Channel tells */
2544                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2545
2546                   if (tkind == 1 && strchr(star_match[0], ':')) {
2547                       /* Avoid "tells you:" spoofs in channels */
2548                      tkind = 3;
2549                   }
2550                   if (star_match[0][0] == NULLCHAR ||
2551                       strchr(star_match[0], ' ') ||
2552                       (tkind == 3 && strchr(star_match[1], ' '))) {
2553                     /* Reject bogus matches */
2554                     i = oldi;
2555                   } else {
2556                     if (appData.colorize) {
2557                       if (oldi > next_out) {
2558                         SendToPlayer(&buf[next_out], oldi - next_out);
2559                         next_out = oldi;
2560                       }
2561                       switch (tkind) {
2562                       case 1:
2563                         Colorize(ColorTell, FALSE);
2564                         curColor = ColorTell;
2565                         break;
2566                       case 2:
2567                         Colorize(ColorKibitz, FALSE);
2568                         curColor = ColorKibitz;
2569                         break;
2570                       case 3:
2571                         p = strrchr(star_match[1], '(');
2572                         if (p == NULL) {
2573                           p = star_match[1];
2574                         } else {
2575                           p++;
2576                         }
2577                         if (atoi(p) == 1) {
2578                           Colorize(ColorChannel1, FALSE);
2579                           curColor = ColorChannel1;
2580                         } else {
2581                           Colorize(ColorChannel, FALSE);
2582                           curColor = ColorChannel;
2583                         }
2584                         break;
2585                       case 5:
2586                         curColor = ColorNormal;
2587                         break;
2588                       }
2589                     }
2590                     if (started == STARTED_NONE && appData.autoComment &&
2591                         (gameMode == IcsObserving ||
2592                          gameMode == IcsPlayingWhite ||
2593                          gameMode == IcsPlayingBlack)) {
2594                       parse_pos = i - oldi;
2595                       memcpy(parse, &buf[oldi], parse_pos);
2596                       parse[parse_pos] = NULLCHAR;
2597                       started = STARTED_COMMENT;
2598                       savingComment = TRUE;
2599                     } else {
2600                       started = STARTED_CHATTER;
2601                       savingComment = FALSE;
2602                     }
2603                     loggedOn = TRUE;
2604                     continue;
2605                   }
2606                 }
2607
2608                 if (looking_at(buf, &i, "* s-shouts: ") ||
2609                     looking_at(buf, &i, "* c-shouts: ")) {
2610                     if (appData.colorize) {
2611                         if (oldi > next_out) {
2612                             SendToPlayer(&buf[next_out], oldi - next_out);
2613                             next_out = oldi;
2614                         }
2615                         Colorize(ColorSShout, FALSE);
2616                         curColor = ColorSShout;
2617                     }
2618                     loggedOn = TRUE;
2619                     started = STARTED_CHATTER;
2620                     continue;
2621                 }
2622
2623                 if (looking_at(buf, &i, "--->")) {
2624                     loggedOn = TRUE;
2625                     continue;
2626                 }
2627
2628                 if (looking_at(buf, &i, "* shouts: ") ||
2629                     looking_at(buf, &i, "--> ")) {
2630                     if (appData.colorize) {
2631                         if (oldi > next_out) {
2632                             SendToPlayer(&buf[next_out], oldi - next_out);
2633                             next_out = oldi;
2634                         }
2635                         Colorize(ColorShout, FALSE);
2636                         curColor = ColorShout;
2637                     }
2638                     loggedOn = TRUE;
2639                     started = STARTED_CHATTER;
2640                     continue;
2641                 }
2642
2643                 if (looking_at( buf, &i, "Challenge:")) {
2644                     if (appData.colorize) {
2645                         if (oldi > next_out) {
2646                             SendToPlayer(&buf[next_out], oldi - next_out);
2647                             next_out = oldi;
2648                         }
2649                         Colorize(ColorChallenge, FALSE);
2650                         curColor = ColorChallenge;
2651                     }
2652                     loggedOn = TRUE;
2653                     continue;
2654                 }
2655
2656                 if (looking_at(buf, &i, "* offers you") ||
2657                     looking_at(buf, &i, "* offers to be") ||
2658                     looking_at(buf, &i, "* would like to") ||
2659                     looking_at(buf, &i, "* requests to") ||
2660                     looking_at(buf, &i, "Your opponent offers") ||
2661                     looking_at(buf, &i, "Your opponent requests")) {
2662
2663                     if (appData.colorize) {
2664                         if (oldi > next_out) {
2665                             SendToPlayer(&buf[next_out], oldi - next_out);
2666                             next_out = oldi;
2667                         }
2668                         Colorize(ColorRequest, FALSE);
2669                         curColor = ColorRequest;
2670                     }
2671                     continue;
2672                 }
2673
2674                 if (looking_at(buf, &i, "* (*) seeking")) {
2675                     if (appData.colorize) {
2676                         if (oldi > next_out) {
2677                             SendToPlayer(&buf[next_out], oldi - next_out);
2678                             next_out = oldi;
2679                         }
2680                         Colorize(ColorSeek, FALSE);
2681                         curColor = ColorSeek;
2682                     }
2683                     continue;
2684             }
2685
2686             if (looking_at(buf, &i, "\\   ")) {
2687                 if (prevColor != ColorNormal) {
2688                     if (oldi > next_out) {
2689                         SendToPlayer(&buf[next_out], oldi - next_out);
2690                         next_out = oldi;
2691                     }
2692                     Colorize(prevColor, TRUE);
2693                     curColor = prevColor;
2694                 }
2695                 if (savingComment) {
2696                     parse_pos = i - oldi;
2697                     memcpy(parse, &buf[oldi], parse_pos);
2698                     parse[parse_pos] = NULLCHAR;
2699                     started = STARTED_COMMENT;
2700                 } else {
2701                     started = STARTED_CHATTER;
2702                 }
2703                 continue;
2704             }
2705
2706             if (looking_at(buf, &i, "Black Strength :") ||
2707                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2708                 looking_at(buf, &i, "<10>") ||
2709                 looking_at(buf, &i, "#@#")) {
2710                 /* Wrong board style */
2711                 loggedOn = TRUE;
2712                 SendToICS(ics_prefix);
2713                 SendToICS("set style 12\n");
2714                 SendToICS(ics_prefix);
2715                 SendToICS("refresh\n");
2716                 continue;
2717             }
2718
2719             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2720                 ICSInitScript();
2721                 have_sent_ICS_logon = 1;
2722                 continue;
2723             }
2724
2725             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2726                 (looking_at(buf, &i, "\n<12> ") ||
2727                  looking_at(buf, &i, "<12> "))) {
2728                 loggedOn = TRUE;
2729                 if (oldi > next_out) {
2730                     SendToPlayer(&buf[next_out], oldi - next_out);
2731                 }
2732                 next_out = i;
2733                 started = STARTED_BOARD;
2734                 parse_pos = 0;
2735                 continue;
2736             }
2737
2738             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2739                 looking_at(buf, &i, "<b1> ")) {
2740                 if (oldi > next_out) {
2741                     SendToPlayer(&buf[next_out], oldi - next_out);
2742                 }
2743                 next_out = i;
2744                 started = STARTED_HOLDINGS;
2745                 parse_pos = 0;
2746                 continue;
2747             }
2748
2749             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2750                 loggedOn = TRUE;
2751                 /* Header for a move list -- first line */
2752
2753                 switch (ics_getting_history) {
2754                   case H_FALSE:
2755                     switch (gameMode) {
2756                       case IcsIdle:
2757                       case BeginningOfGame:
2758                         /* User typed "moves" or "oldmoves" while we
2759                            were idle.  Pretend we asked for these
2760                            moves and soak them up so user can step
2761                            through them and/or save them.
2762                            */
2763                         Reset(FALSE, TRUE);
2764                         gameMode = IcsObserving;
2765                         ModeHighlight();
2766                         ics_gamenum = -1;
2767                         ics_getting_history = H_GOT_UNREQ_HEADER;
2768                         break;
2769                       case EditGame: /*?*/
2770                       case EditPosition: /*?*/
2771                         /* Should above feature work in these modes too? */
2772                         /* For now it doesn't */
2773                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2774                         break;
2775                       default:
2776                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2777                         break;
2778                     }
2779                     break;
2780                   case H_REQUESTED:
2781                     /* Is this the right one? */
2782                     if (gameInfo.white && gameInfo.black &&
2783                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2784                         strcmp(gameInfo.black, star_match[2]) == 0) {
2785                         /* All is well */
2786                         ics_getting_history = H_GOT_REQ_HEADER;
2787                     }
2788                     break;
2789                   case H_GOT_REQ_HEADER:
2790                   case H_GOT_UNREQ_HEADER:
2791                   case H_GOT_UNWANTED_HEADER:
2792                   case H_GETTING_MOVES:
2793                     /* Should not happen */
2794                     DisplayError(_("Error gathering move list: two headers"), 0);
2795                     ics_getting_history = H_FALSE;
2796                     break;
2797                 }
2798
2799                 /* Save player ratings into gameInfo if needed */
2800                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2801                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2802                     (gameInfo.whiteRating == -1 ||
2803                      gameInfo.blackRating == -1)) {
2804
2805                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2806                     gameInfo.blackRating = string_to_rating(star_match[3]);
2807                     if (appData.debugMode)
2808                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2809                               gameInfo.whiteRating, gameInfo.blackRating);
2810                 }
2811                 continue;
2812             }
2813
2814             if (looking_at(buf, &i,
2815               "* * match, initial time: * minute*, increment: * second")) {
2816                 /* Header for a move list -- second line */
2817                 /* Initial board will follow if this is a wild game */
2818                 if (gameInfo.event != NULL) free(gameInfo.event);
2819                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2820                 gameInfo.event = StrSave(str);
2821                 /* [HGM] we switched variant. Translate boards if needed. */
2822                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2823                 continue;
2824             }
2825
2826             if (looking_at(buf, &i, "Move  ")) {
2827                 /* Beginning of a move list */
2828                 switch (ics_getting_history) {
2829                   case H_FALSE:
2830                     /* Normally should not happen */
2831                     /* Maybe user hit reset while we were parsing */
2832                     break;
2833                   case H_REQUESTED:
2834                     /* Happens if we are ignoring a move list that is not
2835                      * the one we just requested.  Common if the user
2836                      * tries to observe two games without turning off
2837                      * getMoveList */
2838                     break;
2839                   case H_GETTING_MOVES:
2840                     /* Should not happen */
2841                     DisplayError(_("Error gathering move list: nested"), 0);
2842                     ics_getting_history = H_FALSE;
2843                     break;
2844                   case H_GOT_REQ_HEADER:
2845                     ics_getting_history = H_GETTING_MOVES;
2846                     started = STARTED_MOVES;
2847                     parse_pos = 0;
2848                     if (oldi > next_out) {
2849                         SendToPlayer(&buf[next_out], oldi - next_out);
2850                     }
2851                     break;
2852                   case H_GOT_UNREQ_HEADER:
2853                     ics_getting_history = H_GETTING_MOVES;
2854                     started = STARTED_MOVES_NOHIDE;
2855                     parse_pos = 0;
2856                     break;
2857                   case H_GOT_UNWANTED_HEADER:
2858                     ics_getting_history = H_FALSE;
2859                     break;
2860                 }
2861                 continue;
2862             }
2863
2864             if (looking_at(buf, &i, "% ") ||
2865                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2866                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2867                 if(suppressKibitz) next_out = i;
2868                 savingComment = FALSE;
2869                 suppressKibitz = 0;
2870                 switch (started) {
2871                   case STARTED_MOVES:
2872                   case STARTED_MOVES_NOHIDE:
2873                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2874                     parse[parse_pos + i - oldi] = NULLCHAR;
2875                     ParseGameHistory(parse);
2876 #if ZIPPY
2877                     if (appData.zippyPlay && first.initDone) {
2878                         FeedMovesToProgram(&first, forwardMostMove);
2879                         if (gameMode == IcsPlayingWhite) {
2880                             if (WhiteOnMove(forwardMostMove)) {
2881                                 if (first.sendTime) {
2882                                   if (first.useColors) {
2883                                     SendToProgram("black\n", &first);
2884                                   }
2885                                   SendTimeRemaining(&first, TRUE);
2886                                 }
2887                                 if (first.useColors) {
2888                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2889                                 }
2890                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2891                                 first.maybeThinking = TRUE;
2892                             } else {
2893                                 if (first.usePlayother) {
2894                                   if (first.sendTime) {
2895                                     SendTimeRemaining(&first, TRUE);
2896                                   }
2897                                   SendToProgram("playother\n", &first);
2898                                   firstMove = FALSE;
2899                                 } else {
2900                                   firstMove = TRUE;
2901                                 }
2902                             }
2903                         } else if (gameMode == IcsPlayingBlack) {
2904                             if (!WhiteOnMove(forwardMostMove)) {
2905                                 if (first.sendTime) {
2906                                   if (first.useColors) {
2907                                     SendToProgram("white\n", &first);
2908                                   }
2909                                   SendTimeRemaining(&first, FALSE);
2910                                 }
2911                                 if (first.useColors) {
2912                                   SendToProgram("black\n", &first);
2913                                 }
2914                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2915                                 first.maybeThinking = TRUE;
2916                             } else {
2917                                 if (first.usePlayother) {
2918                                   if (first.sendTime) {
2919                                     SendTimeRemaining(&first, FALSE);
2920                                   }
2921                                   SendToProgram("playother\n", &first);
2922                                   firstMove = FALSE;
2923                                 } else {
2924                                   firstMove = TRUE;
2925                                 }
2926                             }
2927                         }
2928                     }
2929 #endif
2930                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2931                         /* Moves came from oldmoves or moves command
2932                            while we weren't doing anything else.
2933                            */
2934                         currentMove = forwardMostMove;
2935                         ClearHighlights();/*!!could figure this out*/
2936                         flipView = appData.flipView;
2937                         DrawPosition(TRUE, boards[currentMove]);
2938                         DisplayBothClocks();
2939                         sprintf(str, "%s vs. %s",
2940                                 gameInfo.white, gameInfo.black);
2941                         DisplayTitle(str);
2942                         gameMode = IcsIdle;
2943                     } else {
2944                         /* Moves were history of an active game */
2945                         if (gameInfo.resultDetails != NULL) {
2946                             free(gameInfo.resultDetails);
2947                             gameInfo.resultDetails = NULL;
2948                         }
2949                     }
2950                     HistorySet(parseList, backwardMostMove,
2951                                forwardMostMove, currentMove-1);
2952                     DisplayMove(currentMove - 1);
2953                     if (started == STARTED_MOVES) next_out = i;
2954                     started = STARTED_NONE;
2955                     ics_getting_history = H_FALSE;
2956                     break;
2957
2958                   case STARTED_OBSERVE:
2959                     started = STARTED_NONE;
2960                     SendToICS(ics_prefix);
2961                     SendToICS("refresh\n");
2962                     break;
2963
2964                   default:
2965                     break;
2966                 }
2967                 if(bookHit) { // [HGM] book: simulate book reply
2968                     static char bookMove[MSG_SIZ]; // a bit generous?
2969
2970                     programStats.nodes = programStats.depth = programStats.time =
2971                     programStats.score = programStats.got_only_move = 0;
2972                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2973
2974                     strcpy(bookMove, "move ");
2975                     strcat(bookMove, bookHit);
2976                     HandleMachineMove(bookMove, &first);
2977                 }
2978                 continue;
2979             }
2980
2981             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2982                  started == STARTED_HOLDINGS ||
2983                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2984                 /* Accumulate characters in move list or board */
2985                 parse[parse_pos++] = buf[i];
2986             }
2987
2988             /* Start of game messages.  Mostly we detect start of game
2989                when the first board image arrives.  On some versions
2990                of the ICS, though, we need to do a "refresh" after starting
2991                to observe in order to get the current board right away. */
2992             if (looking_at(buf, &i, "Adding game * to observation list")) {
2993                 started = STARTED_OBSERVE;
2994                 continue;
2995             }
2996
2997             /* Handle auto-observe */
2998             if (appData.autoObserve &&
2999                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3000                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3001                 char *player;
3002                 /* Choose the player that was highlighted, if any. */
3003                 if (star_match[0][0] == '\033' ||
3004                     star_match[1][0] != '\033') {
3005                     player = star_match[0];
3006                 } else {
3007                     player = star_match[2];
3008                 }
3009                 sprintf(str, "%sobserve %s\n",
3010                         ics_prefix, StripHighlightAndTitle(player));
3011                 SendToICS(str);
3012
3013                 /* Save ratings from notify string */
3014                 strcpy(player1Name, star_match[0]);
3015                 player1Rating = string_to_rating(star_match[1]);
3016                 strcpy(player2Name, star_match[2]);
3017                 player2Rating = string_to_rating(star_match[3]);
3018
3019                 if (appData.debugMode)
3020                   fprintf(debugFP,
3021                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3022                           player1Name, player1Rating,
3023                           player2Name, player2Rating);
3024
3025                 continue;
3026             }
3027
3028             /* Deal with automatic examine mode after a game,
3029                and with IcsObserving -> IcsExamining transition */
3030             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3031                 looking_at(buf, &i, "has made you an examiner of game *")) {
3032
3033                 int gamenum = atoi(star_match[0]);
3034                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3035                     gamenum == ics_gamenum) {
3036                     /* We were already playing or observing this game;
3037                        no need to refetch history */
3038                     gameMode = IcsExamining;
3039                     if (pausing) {
3040                         pauseExamForwardMostMove = forwardMostMove;
3041                     } else if (currentMove < forwardMostMove) {
3042                         ForwardInner(forwardMostMove);
3043                     }
3044                 } else {
3045                     /* I don't think this case really can happen */
3046                     SendToICS(ics_prefix);
3047                     SendToICS("refresh\n");
3048                 }
3049                 continue;
3050             }
3051
3052             /* Error messages */
3053 //          if (ics_user_moved) {
3054             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3055                 if (looking_at(buf, &i, "Illegal move") ||
3056                     looking_at(buf, &i, "Not a legal move") ||
3057                     looking_at(buf, &i, "Your king is in check") ||
3058                     looking_at(buf, &i, "It isn't your turn") ||
3059                     looking_at(buf, &i, "It is not your move")) {
3060                     /* Illegal move */
3061                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3062                         currentMove = --forwardMostMove;
3063                         DisplayMove(currentMove - 1); /* before DMError */
3064                         DrawPosition(FALSE, boards[currentMove]);
3065                         SwitchClocks();
3066                         DisplayBothClocks();
3067                     }
3068                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3069                     ics_user_moved = 0;
3070                     continue;
3071                 }
3072             }
3073
3074             if (looking_at(buf, &i, "still have time") ||
3075                 looking_at(buf, &i, "not out of time") ||
3076                 looking_at(buf, &i, "either player is out of time") ||
3077                 looking_at(buf, &i, "has timeseal; checking")) {
3078                 /* We must have called his flag a little too soon */
3079                 whiteFlag = blackFlag = FALSE;
3080                 continue;
3081             }
3082
3083             if (looking_at(buf, &i, "added * seconds to") ||
3084                 looking_at(buf, &i, "seconds were added to")) {
3085                 /* Update the clocks */
3086                 SendToICS(ics_prefix);
3087                 SendToICS("refresh\n");
3088                 continue;
3089             }
3090
3091             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3092                 ics_clock_paused = TRUE;
3093                 StopClocks();
3094                 continue;
3095             }
3096
3097             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3098                 ics_clock_paused = FALSE;
3099                 StartClocks();
3100                 continue;
3101             }
3102
3103             /* Grab player ratings from the Creating: message.
3104                Note we have to check for the special case when
3105                the ICS inserts things like [white] or [black]. */
3106             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3107                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3108                 /* star_matches:
3109                    0    player 1 name (not necessarily white)
3110                    1    player 1 rating
3111                    2    empty, white, or black (IGNORED)
3112                    3    player 2 name (not necessarily black)
3113                    4    player 2 rating
3114
3115                    The names/ratings are sorted out when the game
3116                    actually starts (below).
3117                 */
3118                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3119                 player1Rating = string_to_rating(star_match[1]);
3120                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3121                 player2Rating = string_to_rating(star_match[4]);
3122
3123                 if (appData.debugMode)
3124                   fprintf(debugFP,
3125                           "Ratings from 'Creating:' %s %d, %s %d\n",
3126                           player1Name, player1Rating,
3127                           player2Name, player2Rating);
3128
3129                 continue;
3130             }
3131
3132             /* Improved generic start/end-of-game messages */
3133             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3134                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3135                 /* If tkind == 0: */
3136                 /* star_match[0] is the game number */
3137                 /*           [1] is the white player's name */
3138                 /*           [2] is the black player's name */
3139                 /* For end-of-game: */
3140                 /*           [3] is the reason for the game end */
3141                 /*           [4] is a PGN end game-token, preceded by " " */
3142                 /* For start-of-game: */
3143                 /*           [3] begins with "Creating" or "Continuing" */
3144                 /*           [4] is " *" or empty (don't care). */
3145                 int gamenum = atoi(star_match[0]);
3146                 char *whitename, *blackname, *why, *endtoken;
3147                 ChessMove endtype = (ChessMove) 0;
3148
3149                 if (tkind == 0) {
3150                   whitename = star_match[1];
3151                   blackname = star_match[2];
3152                   why = star_match[3];
3153                   endtoken = star_match[4];
3154                 } else {
3155                   whitename = star_match[1];
3156                   blackname = star_match[3];
3157                   why = star_match[5];
3158                   endtoken = star_match[6];
3159                 }
3160
3161                 /* Game start messages */
3162                 if (strncmp(why, "Creating ", 9) == 0 ||
3163                     strncmp(why, "Continuing ", 11) == 0) {
3164                     gs_gamenum = gamenum;
3165                     strcpy(gs_kind, strchr(why, ' ') + 1);
3166                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3167 #if ZIPPY
3168                     if (appData.zippyPlay) {
3169                         ZippyGameStart(whitename, blackname);
3170                     }
3171 #endif /*ZIPPY*/
3172                     continue;
3173                 }
3174
3175                 /* Game end messages */
3176                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3177                     ics_gamenum != gamenum) {
3178                     continue;
3179                 }
3180                 while (endtoken[0] == ' ') endtoken++;
3181                 switch (endtoken[0]) {
3182                   case '*':
3183                   default:
3184                     endtype = GameUnfinished;
3185                     break;
3186                   case '0':
3187                     endtype = BlackWins;
3188                     break;
3189                   case '1':
3190                     if (endtoken[1] == '/')
3191                       endtype = GameIsDrawn;
3192                     else
3193                       endtype = WhiteWins;
3194                     break;
3195                 }
3196                 GameEnds(endtype, why, GE_ICS);
3197 #if ZIPPY
3198                 if (appData.zippyPlay && first.initDone) {
3199                     ZippyGameEnd(endtype, why);
3200                     if (first.pr == NULL) {
3201                       /* Start the next process early so that we'll
3202                          be ready for the next challenge */
3203                       StartChessProgram(&first);
3204                     }
3205                     /* Send "new" early, in case this command takes
3206                        a long time to finish, so that we'll be ready
3207                        for the next challenge. */
3208                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3209                     Reset(TRUE, TRUE);
3210                 }
3211 #endif /*ZIPPY*/
3212                 continue;
3213             }
3214
3215             if (looking_at(buf, &i, "Removing game * from observation") ||
3216                 looking_at(buf, &i, "no longer observing game *") ||
3217                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3218                 if (gameMode == IcsObserving &&
3219                     atoi(star_match[0]) == ics_gamenum)
3220                   {
3221                       /* icsEngineAnalyze */
3222                       if (appData.icsEngineAnalyze) {
3223                             ExitAnalyzeMode();
3224                             ModeHighlight();
3225                       }
3226                       StopClocks();
3227                       gameMode = IcsIdle;
3228                       ics_gamenum = -1;
3229                       ics_user_moved = FALSE;
3230                   }
3231                 continue;
3232             }
3233
3234             if (looking_at(buf, &i, "no longer examining game *")) {
3235                 if (gameMode == IcsExamining &&
3236                     atoi(star_match[0]) == ics_gamenum)
3237                   {
3238                       gameMode = IcsIdle;
3239                       ics_gamenum = -1;
3240                       ics_user_moved = FALSE;
3241                   }
3242                 continue;
3243             }
3244
3245             /* Advance leftover_start past any newlines we find,
3246                so only partial lines can get reparsed */
3247             if (looking_at(buf, &i, "\n")) {
3248                 prevColor = curColor;
3249                 if (curColor != ColorNormal) {
3250                     if (oldi > next_out) {
3251                         SendToPlayer(&buf[next_out], oldi - next_out);
3252                         next_out = oldi;
3253                     }
3254                     Colorize(ColorNormal, FALSE);
3255                     curColor = ColorNormal;
3256                 }
3257                 if (started == STARTED_BOARD) {
3258                     started = STARTED_NONE;
3259                     parse[parse_pos] = NULLCHAR;
3260                     ParseBoard12(parse);
3261                     ics_user_moved = 0;
3262
3263                     /* Send premove here */
3264                     if (appData.premove) {
3265                       char str[MSG_SIZ];
3266                       if (currentMove == 0 &&
3267                           gameMode == IcsPlayingWhite &&
3268                           appData.premoveWhite) {
3269                         sprintf(str, "%s\n", appData.premoveWhiteText);
3270                         if (appData.debugMode)
3271                           fprintf(debugFP, "Sending premove:\n");
3272                         SendToICS(str);
3273                       } else if (currentMove == 1 &&
3274                                  gameMode == IcsPlayingBlack &&
3275                                  appData.premoveBlack) {
3276                         sprintf(str, "%s\n", appData.premoveBlackText);
3277                         if (appData.debugMode)
3278                           fprintf(debugFP, "Sending premove:\n");
3279                         SendToICS(str);
3280                       } else if (gotPremove) {
3281                         gotPremove = 0;
3282                         ClearPremoveHighlights();
3283                         if (appData.debugMode)
3284                           fprintf(debugFP, "Sending premove:\n");
3285                           UserMoveEvent(premoveFromX, premoveFromY,
3286                                         premoveToX, premoveToY,
3287                                         premovePromoChar);
3288                       }
3289                     }
3290
3291                     /* Usually suppress following prompt */
3292                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3293                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3294                         if (looking_at(buf, &i, "*% ")) {
3295                             savingComment = FALSE;
3296                             suppressKibitz = 0;
3297                         }
3298                     }
3299                     next_out = i;
3300                 } else if (started == STARTED_HOLDINGS) {
3301                     int gamenum;
3302                     char new_piece[MSG_SIZ];
3303                     started = STARTED_NONE;
3304                     parse[parse_pos] = NULLCHAR;
3305                     if (appData.debugMode)
3306                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3307                                                         parse, currentMove);
3308                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3309                         gamenum == ics_gamenum) {
3310                         if (gameInfo.variant == VariantNormal) {
3311                           /* [HGM] We seem to switch variant during a game!
3312                            * Presumably no holdings were displayed, so we have
3313                            * to move the position two files to the right to
3314                            * create room for them!
3315                            */
3316                           VariantClass newVariant;
3317                           switch(gameInfo.boardWidth) { // base guess on board width
3318                                 case 9:  newVariant = VariantShogi; break;
3319                                 case 10: newVariant = VariantGreat; break;
3320                                 default: newVariant = VariantCrazyhouse; break;
3321                           }
3322                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3323                           /* Get a move list just to see the header, which
3324                              will tell us whether this is really bug or zh */
3325                           if (ics_getting_history == H_FALSE) {
3326                             ics_getting_history = H_REQUESTED;
3327                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3328                             SendToICS(str);
3329                           }
3330                         }
3331                         new_piece[0] = NULLCHAR;
3332                         sscanf(parse, "game %d white [%s black [%s <- %s",
3333                                &gamenum, white_holding, black_holding,
3334                                new_piece);
3335                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3336                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3337                         /* [HGM] copy holdings to board holdings area */
3338                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3339                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3340                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3341 #if ZIPPY
3342                         if (appData.zippyPlay && first.initDone) {
3343                             ZippyHoldings(white_holding, black_holding,
3344                                           new_piece);
3345                         }
3346 #endif /*ZIPPY*/
3347                         if (tinyLayout || smallLayout) {
3348                             char wh[16], bh[16];
3349                             PackHolding(wh, white_holding);
3350                             PackHolding(bh, black_holding);
3351                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3352                                     gameInfo.white, gameInfo.black);
3353                         } else {
3354                             sprintf(str, "%s [%s] vs. %s [%s]",
3355                                     gameInfo.white, white_holding,
3356                                     gameInfo.black, black_holding);
3357                         }
3358
3359                         DrawPosition(FALSE, boards[currentMove]);
3360                         DisplayTitle(str);
3361                     }
3362                     /* Suppress following prompt */
3363                     if (looking_at(buf, &i, "*% ")) {
3364                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3365                         savingComment = FALSE;
3366                         suppressKibitz = 0;
3367                     }
3368                     next_out = i;
3369                 }
3370                 continue;
3371             }
3372
3373             i++;                /* skip unparsed character and loop back */
3374         }
3375         
3376         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3377 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3378 //          SendToPlayer(&buf[next_out], i - next_out);
3379             started != STARTED_HOLDINGS && leftover_start > next_out) {
3380             SendToPlayer(&buf[next_out], leftover_start - next_out);
3381             next_out = i;
3382         }
3383
3384         leftover_len = buf_len - leftover_start;
3385         /* if buffer ends with something we couldn't parse,
3386            reparse it after appending the next read */
3387
3388     } else if (count == 0) {
3389         RemoveInputSource(isr);
3390         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3391     } else {
3392         DisplayFatalError(_("Error reading from ICS"), error, 1);
3393     }
3394 }
3395
3396
3397 /* Board style 12 looks like this:
3398
3399    <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
3400
3401  * The "<12> " is stripped before it gets to this routine.  The two
3402  * trailing 0's (flip state and clock ticking) are later addition, and
3403  * some chess servers may not have them, or may have only the first.
3404  * Additional trailing fields may be added in the future.
3405  */
3406
3407 #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"
3408
3409 #define RELATION_OBSERVING_PLAYED    0
3410 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3411 #define RELATION_PLAYING_MYMOVE      1
3412 #define RELATION_PLAYING_NOTMYMOVE  -1
3413 #define RELATION_EXAMINING           2
3414 #define RELATION_ISOLATED_BOARD     -3
3415 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3416
3417 void
3418 ParseBoard12(string)
3419      char *string;
3420 {
3421     GameMode newGameMode;
3422     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3423     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3424     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3425     char to_play, board_chars[200];
3426     char move_str[500], str[500], elapsed_time[500];
3427     char black[32], white[32];
3428     Board board;
3429     int prevMove = currentMove;
3430     int ticking = 2;
3431     ChessMove moveType;
3432     int fromX, fromY, toX, toY;
3433     char promoChar;
3434     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3435     char *bookHit = NULL; // [HGM] book
3436     Boolean weird = FALSE, reqFlag = FALSE;
3437
3438     fromX = fromY = toX = toY = -1;
3439
3440     newGame = FALSE;
3441
3442     if (appData.debugMode)
3443       fprintf(debugFP, _("Parsing board: %s\n"), string);
3444
3445     move_str[0] = NULLCHAR;
3446     elapsed_time[0] = NULLCHAR;
3447     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3448         int  i = 0, j;
3449         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3450             if(string[i] == ' ') { ranks++; files = 0; }
3451             else files++;
3452             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3453             i++;
3454         }
3455         for(j = 0; j <i; j++) board_chars[j] = string[j];
3456         board_chars[i] = '\0';
3457         string += i + 1;
3458     }
3459     n = sscanf(string, PATTERN, &to_play, &double_push,
3460                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3461                &gamenum, white, black, &relation, &basetime, &increment,
3462                &white_stren, &black_stren, &white_time, &black_time,
3463                &moveNum, str, elapsed_time, move_str, &ics_flip,
3464                &ticking);
3465
3466     if (n < 21) {
3467         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3468         DisplayError(str, 0);
3469         return;
3470     }
3471
3472     /* Convert the move number to internal form */
3473     moveNum = (moveNum - 1) * 2;
3474     if (to_play == 'B') moveNum++;
3475     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3476       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3477                         0, 1);
3478       return;
3479     }
3480
3481     switch (relation) {
3482       case RELATION_OBSERVING_PLAYED:
3483       case RELATION_OBSERVING_STATIC:
3484         if (gamenum == -1) {
3485             /* Old ICC buglet */
3486             relation = RELATION_OBSERVING_STATIC;
3487         }
3488         newGameMode = IcsObserving;
3489         break;
3490       case RELATION_PLAYING_MYMOVE:
3491       case RELATION_PLAYING_NOTMYMOVE:
3492         newGameMode =
3493           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3494             IcsPlayingWhite : IcsPlayingBlack;
3495         break;
3496       case RELATION_EXAMINING:
3497         newGameMode = IcsExamining;
3498         break;
3499       case RELATION_ISOLATED_BOARD:
3500       default:
3501         /* Just display this board.  If user was doing something else,
3502            we will forget about it until the next board comes. */
3503         newGameMode = IcsIdle;
3504         break;
3505       case RELATION_STARTING_POSITION:
3506         newGameMode = gameMode;
3507         break;
3508     }
3509
3510     /* Modify behavior for initial board display on move listing
3511        of wild games.
3512        */
3513     switch (ics_getting_history) {
3514       case H_FALSE:
3515       case H_REQUESTED:
3516         break;
3517       case H_GOT_REQ_HEADER:
3518       case H_GOT_UNREQ_HEADER:
3519         /* This is the initial position of the current game */
3520         gamenum = ics_gamenum;
3521         moveNum = 0;            /* old ICS bug workaround */
3522         if (to_play == 'B') {
3523           startedFromSetupPosition = TRUE;
3524           blackPlaysFirst = TRUE;
3525           moveNum = 1;
3526           if (forwardMostMove == 0) forwardMostMove = 1;
3527           if (backwardMostMove == 0) backwardMostMove = 1;
3528           if (currentMove == 0) currentMove = 1;
3529         }
3530         newGameMode = gameMode;
3531         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3532         break;
3533       case H_GOT_UNWANTED_HEADER:
3534         /* This is an initial board that we don't want */
3535         return;
3536       case H_GETTING_MOVES:
3537         /* Should not happen */
3538         DisplayError(_("Error gathering move list: extra board"), 0);
3539         ics_getting_history = H_FALSE;
3540         return;
3541     }
3542
3543    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3544                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3545      /* [HGM] We seem to have switched variant unexpectedly
3546       * Try to guess new variant from board size
3547       */
3548           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3549           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3550           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3551           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3552           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3553           if(!weird) newVariant = VariantNormal;
3554           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3555           /* Get a move list just to see the header, which
3556              will tell us whether this is really bug or zh */
3557           if (ics_getting_history == H_FALSE) {
3558             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3559             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3560             SendToICS(str);
3561           }
3562     }
3563     
3564     /* Take action if this is the first board of a new game, or of a
3565        different game than is currently being displayed.  */
3566     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3567         relation == RELATION_ISOLATED_BOARD) {
3568
3569         /* Forget the old game and get the history (if any) of the new one */
3570         if (gameMode != BeginningOfGame) {
3571           Reset(TRUE, TRUE);
3572         }
3573         newGame = TRUE;
3574         if (appData.autoRaiseBoard) BoardToTop();
3575         prevMove = -3;
3576         if (gamenum == -1) {
3577             newGameMode = IcsIdle;
3578         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3579                    appData.getMoveList && !reqFlag) {
3580             /* Need to get game history */
3581             ics_getting_history = H_REQUESTED;
3582             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3583             SendToICS(str);
3584         }
3585
3586         /* Initially flip the board to have black on the bottom if playing
3587            black or if the ICS flip flag is set, but let the user change
3588            it with the Flip View button. */
3589         flipView = appData.autoFlipView ?
3590           (newGameMode == IcsPlayingBlack) || ics_flip :
3591           appData.flipView;
3592
3593         /* Done with values from previous mode; copy in new ones */
3594         gameMode = newGameMode;
3595         ModeHighlight();
3596         ics_gamenum = gamenum;
3597         if (gamenum == gs_gamenum) {
3598             int klen = strlen(gs_kind);
3599             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3600             sprintf(str, "ICS %s", gs_kind);
3601             gameInfo.event = StrSave(str);
3602         } else {
3603             gameInfo.event = StrSave("ICS game");
3604         }
3605         gameInfo.site = StrSave(appData.icsHost);
3606         gameInfo.date = PGNDate();
3607         gameInfo.round = StrSave("-");
3608         gameInfo.white = StrSave(white);
3609         gameInfo.black = StrSave(black);
3610         timeControl = basetime * 60 * 1000;
3611         timeControl_2 = 0;
3612         timeIncrement = increment * 1000;
3613         movesPerSession = 0;
3614         gameInfo.timeControl = TimeControlTagValue();
3615         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3616   if (appData.debugMode) {
3617     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3618     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3619     setbuf(debugFP, NULL);
3620   }
3621
3622         gameInfo.outOfBook = NULL;
3623
3624         /* Do we have the ratings? */
3625         if (strcmp(player1Name, white) == 0 &&
3626             strcmp(player2Name, black) == 0) {
3627             if (appData.debugMode)
3628               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3629                       player1Rating, player2Rating);
3630             gameInfo.whiteRating = player1Rating;
3631             gameInfo.blackRating = player2Rating;
3632         } else if (strcmp(player2Name, white) == 0 &&
3633                    strcmp(player1Name, black) == 0) {
3634             if (appData.debugMode)
3635               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3636                       player2Rating, player1Rating);
3637             gameInfo.whiteRating = player2Rating;
3638             gameInfo.blackRating = player1Rating;
3639         }
3640         player1Name[0] = player2Name[0] = NULLCHAR;
3641
3642         /* Silence shouts if requested */
3643         if (appData.quietPlay &&
3644             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3645             SendToICS(ics_prefix);
3646             SendToICS("set shout 0\n");
3647         }
3648     }
3649
3650     /* Deal with midgame name changes */
3651     if (!newGame) {
3652         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3653             if (gameInfo.white) free(gameInfo.white);
3654             gameInfo.white = StrSave(white);
3655         }
3656         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3657             if (gameInfo.black) free(gameInfo.black);
3658             gameInfo.black = StrSave(black);
3659         }
3660     }
3661
3662     /* Throw away game result if anything actually changes in examine mode */
3663     if (gameMode == IcsExamining && !newGame) {
3664         gameInfo.result = GameUnfinished;
3665         if (gameInfo.resultDetails != NULL) {
3666             free(gameInfo.resultDetails);
3667             gameInfo.resultDetails = NULL;
3668         }
3669     }
3670
3671     /* In pausing && IcsExamining mode, we ignore boards coming
3672        in if they are in a different variation than we are. */
3673     if (pauseExamInvalid) return;
3674     if (pausing && gameMode == IcsExamining) {
3675         if (moveNum <= pauseExamForwardMostMove) {
3676             pauseExamInvalid = TRUE;
3677             forwardMostMove = pauseExamForwardMostMove;
3678             return;
3679         }
3680     }
3681
3682   if (appData.debugMode) {
3683     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3684   }
3685     /* Parse the board */
3686     for (k = 0; k < ranks; k++) {
3687       for (j = 0; j < files; j++)
3688         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3689       if(gameInfo.holdingsWidth > 1) {
3690            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3691            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3692       }
3693     }
3694     CopyBoard(boards[moveNum], board);
3695     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3696     if (moveNum == 0) {
3697         startedFromSetupPosition =
3698           !CompareBoards(board, initialPosition);
3699         if(startedFromSetupPosition)
3700             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3701     }
3702
3703     /* [HGM] Set castling rights. Take the outermost Rooks,
3704        to make it also work for FRC opening positions. Note that board12
3705        is really defective for later FRC positions, as it has no way to
3706        indicate which Rook can castle if they are on the same side of King.
3707        For the initial position we grant rights to the outermost Rooks,
3708        and remember thos rights, and we then copy them on positions
3709        later in an FRC game. This means WB might not recognize castlings with
3710        Rooks that have moved back to their original position as illegal,
3711        but in ICS mode that is not its job anyway.
3712     */
3713     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3714     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3715
3716         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3717             if(board[0][i] == WhiteRook) j = i;
3718         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3719         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3720             if(board[0][i] == WhiteRook) j = i;
3721         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3722         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3723             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3724         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3725         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3726             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3727         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3728
3729         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3730         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3731             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3732         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3733             if(board[BOARD_HEIGHT-1][k] == bKing)
3734                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3735         if(gameInfo.variant == VariantTwoKings) {
3736             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3737             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3738             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3739         }
3740     } else { int r;
3741         r = boards[moveNum][CASTLING][0] = initialRights[0];
3742         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3743         r = boards[moveNum][CASTLING][1] = initialRights[1];
3744         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3745         r = boards[moveNum][CASTLING][3] = initialRights[3];
3746         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3747         r = boards[moveNum][CASTLING][4] = initialRights[4];
3748         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3749         /* wildcastle kludge: always assume King has rights */
3750         r = boards[moveNum][CASTLING][2] = initialRights[2];
3751         r = boards[moveNum][CASTLING][5] = initialRights[5];
3752     }
3753     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3754     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3755
3756
3757     if (ics_getting_history == H_GOT_REQ_HEADER ||
3758         ics_getting_history == H_GOT_UNREQ_HEADER) {
3759         /* This was an initial position from a move list, not
3760            the current position */
3761         return;
3762     }
3763
3764     /* Update currentMove and known move number limits */
3765     newMove = newGame || moveNum > forwardMostMove;
3766
3767     if (newGame) {
3768         forwardMostMove = backwardMostMove = currentMove = moveNum;
3769         if (gameMode == IcsExamining && moveNum == 0) {
3770           /* Workaround for ICS limitation: we are not told the wild
3771              type when starting to examine a game.  But if we ask for
3772              the move list, the move list header will tell us */
3773             ics_getting_history = H_REQUESTED;
3774             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3775             SendToICS(str);
3776         }
3777     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3778                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3779 #if ZIPPY
3780         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3781         /* [HGM] applied this also to an engine that is silently watching        */
3782         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3783             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3784             gameInfo.variant == currentlyInitializedVariant) {
3785           takeback = forwardMostMove - moveNum;
3786           for (i = 0; i < takeback; i++) {
3787             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3788             SendToProgram("undo\n", &first);
3789           }
3790         }
3791 #endif
3792
3793         forwardMostMove = moveNum;
3794         if (!pausing || currentMove > forwardMostMove)
3795           currentMove = forwardMostMove;
3796     } else {
3797         /* New part of history that is not contiguous with old part */
3798         if (pausing && gameMode == IcsExamining) {
3799             pauseExamInvalid = TRUE;
3800             forwardMostMove = pauseExamForwardMostMove;
3801             return;
3802         }
3803         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3804 #if ZIPPY
3805             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3806                 // [HGM] when we will receive the move list we now request, it will be
3807                 // fed to the engine from the first move on. So if the engine is not
3808                 // in the initial position now, bring it there.
3809                 InitChessProgram(&first, 0);
3810             }
3811 #endif
3812             ics_getting_history = H_REQUESTED;
3813             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3814             SendToICS(str);
3815         }
3816         forwardMostMove = backwardMostMove = currentMove = moveNum;
3817     }
3818
3819     /* Update the clocks */
3820     if (strchr(elapsed_time, '.')) {
3821       /* Time is in ms */
3822       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3823       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3824     } else {
3825       /* Time is in seconds */
3826       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3827       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3828     }
3829
3830
3831 #if ZIPPY
3832     if (appData.zippyPlay && newGame &&
3833         gameMode != IcsObserving && gameMode != IcsIdle &&
3834         gameMode != IcsExamining)
3835       ZippyFirstBoard(moveNum, basetime, increment);
3836 #endif
3837
3838     /* Put the move on the move list, first converting
3839        to canonical algebraic form. */
3840     if (moveNum > 0) {
3841   if (appData.debugMode) {
3842     if (appData.debugMode) { int f = forwardMostMove;
3843         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3844                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3845                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3846     }
3847     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3848     fprintf(debugFP, "moveNum = %d\n", moveNum);
3849     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3850     setbuf(debugFP, NULL);
3851   }
3852         if (moveNum <= backwardMostMove) {
3853             /* We don't know what the board looked like before
3854                this move.  Punt. */
3855             strcpy(parseList[moveNum - 1], move_str);
3856             strcat(parseList[moveNum - 1], " ");
3857             strcat(parseList[moveNum - 1], elapsed_time);
3858             moveList[moveNum - 1][0] = NULLCHAR;
3859         } else if (strcmp(move_str, "none") == 0) {
3860             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3861             /* Again, we don't know what the board looked like;
3862                this is really the start of the game. */
3863             parseList[moveNum - 1][0] = NULLCHAR;
3864             moveList[moveNum - 1][0] = NULLCHAR;
3865             backwardMostMove = moveNum;
3866             startedFromSetupPosition = TRUE;
3867             fromX = fromY = toX = toY = -1;
3868         } else {
3869           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3870           //                 So we parse the long-algebraic move string in stead of the SAN move
3871           int valid; char buf[MSG_SIZ], *prom;
3872
3873           // str looks something like "Q/a1-a2"; kill the slash
3874           if(str[1] == '/')
3875                 sprintf(buf, "%c%s", str[0], str+2);
3876           else  strcpy(buf, str); // might be castling
3877           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3878                 strcat(buf, prom); // long move lacks promo specification!
3879           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3880                 if(appData.debugMode)
3881                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3882                 strcpy(move_str, buf);
3883           }
3884           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3885                                 &fromX, &fromY, &toX, &toY, &promoChar)
3886                || ParseOneMove(buf, moveNum - 1, &moveType,
3887                                 &fromX, &fromY, &toX, &toY, &promoChar);
3888           // end of long SAN patch
3889           if (valid) {
3890             (void) CoordsToAlgebraic(boards[moveNum - 1],
3891                                      PosFlags(moveNum - 1),
3892                                      fromY, fromX, toY, toX, promoChar,
3893                                      parseList[moveNum-1]);
3894             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3895               case MT_NONE:
3896               case MT_STALEMATE:
3897               default:
3898                 break;
3899               case MT_CHECK:
3900                 if(gameInfo.variant != VariantShogi)
3901                     strcat(parseList[moveNum - 1], "+");
3902                 break;
3903               case MT_CHECKMATE:
3904               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3905                 strcat(parseList[moveNum - 1], "#");
3906                 break;
3907             }
3908             strcat(parseList[moveNum - 1], " ");
3909             strcat(parseList[moveNum - 1], elapsed_time);
3910             /* currentMoveString is set as a side-effect of ParseOneMove */
3911             strcpy(moveList[moveNum - 1], currentMoveString);
3912             strcat(moveList[moveNum - 1], "\n");
3913           } else {
3914             /* Move from ICS was illegal!?  Punt. */
3915   if (appData.debugMode) {
3916     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3917     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3918   }
3919             strcpy(parseList[moveNum - 1], move_str);
3920             strcat(parseList[moveNum - 1], " ");
3921             strcat(parseList[moveNum - 1], elapsed_time);
3922             moveList[moveNum - 1][0] = NULLCHAR;
3923             fromX = fromY = toX = toY = -1;
3924           }
3925         }
3926   if (appData.debugMode) {
3927     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3928     setbuf(debugFP, NULL);
3929   }
3930
3931 #if ZIPPY
3932         /* Send move to chess program (BEFORE animating it). */
3933         if (appData.zippyPlay && !newGame && newMove &&
3934            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3935
3936             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3937                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3938                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3939                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3940                             move_str);
3941                     DisplayError(str, 0);
3942                 } else {
3943                     if (first.sendTime) {
3944                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3945                     }
3946                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3947                     if (firstMove && !bookHit) {
3948                         firstMove = FALSE;
3949                         if (first.useColors) {
3950                           SendToProgram(gameMode == IcsPlayingWhite ?
3951                                         "white\ngo\n" :
3952                                         "black\ngo\n", &first);
3953                         } else {
3954                           SendToProgram("go\n", &first);
3955                         }
3956                         first.maybeThinking = TRUE;
3957                     }
3958                 }
3959             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3960               if (moveList[moveNum - 1][0] == NULLCHAR) {
3961                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3962                 DisplayError(str, 0);
3963               } else {
3964                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3965                 SendMoveToProgram(moveNum - 1, &first);
3966               }
3967             }
3968         }
3969 #endif
3970     }
3971
3972     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3973         /* If move comes from a remote source, animate it.  If it
3974            isn't remote, it will have already been animated. */
3975         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3976             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3977         }
3978         if (!pausing && appData.highlightLastMove) {
3979             SetHighlights(fromX, fromY, toX, toY);
3980         }
3981     }
3982
3983     /* Start the clocks */
3984     whiteFlag = blackFlag = FALSE;
3985     appData.clockMode = !(basetime == 0 && increment == 0);
3986     if (ticking == 0) {
3987       ics_clock_paused = TRUE;
3988       StopClocks();
3989     } else if (ticking == 1) {
3990       ics_clock_paused = FALSE;
3991     }
3992     if (gameMode == IcsIdle ||
3993         relation == RELATION_OBSERVING_STATIC ||
3994         relation == RELATION_EXAMINING ||
3995         ics_clock_paused)
3996       DisplayBothClocks();
3997     else
3998       StartClocks();
3999
4000     /* Display opponents and material strengths */
4001     if (gameInfo.variant != VariantBughouse &&
4002         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4003         if (tinyLayout || smallLayout) {
4004             if(gameInfo.variant == VariantNormal)
4005                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4006                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4007                     basetime, increment);
4008             else
4009                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4010                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4011                     basetime, increment, (int) gameInfo.variant);
4012         } else {
4013             if(gameInfo.variant == VariantNormal)
4014                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4015                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4016                     basetime, increment);
4017             else
4018                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4019                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4020                     basetime, increment, VariantName(gameInfo.variant));
4021         }
4022         DisplayTitle(str);
4023   if (appData.debugMode) {
4024     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4025   }
4026     }
4027
4028
4029     /* Display the board */
4030     if (!pausing && !appData.noGUI) {
4031       if (appData.premove)
4032           if (!gotPremove ||
4033              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4034              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4035               ClearPremoveHighlights();
4036
4037       DrawPosition(FALSE, boards[currentMove]);
4038       DisplayMove(moveNum - 1);
4039       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4040             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4041               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4042         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4043       }
4044     }
4045
4046     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4047 #if ZIPPY
4048     if(bookHit) { // [HGM] book: simulate book reply
4049         static char bookMove[MSG_SIZ]; // a bit generous?
4050
4051         programStats.nodes = programStats.depth = programStats.time =
4052         programStats.score = programStats.got_only_move = 0;
4053         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4054
4055         strcpy(bookMove, "move ");
4056         strcat(bookMove, bookHit);
4057         HandleMachineMove(bookMove, &first);
4058     }
4059 #endif
4060 }
4061
4062 void
4063 GetMoveListEvent()
4064 {
4065     char buf[MSG_SIZ];
4066     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4067         ics_getting_history = H_REQUESTED;
4068         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4069         SendToICS(buf);
4070     }
4071 }
4072
4073 void
4074 AnalysisPeriodicEvent(force)
4075      int force;
4076 {
4077     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4078          && !force) || !appData.periodicUpdates)
4079       return;
4080
4081     /* Send . command to Crafty to collect stats */
4082     SendToProgram(".\n", &first);
4083
4084     /* Don't send another until we get a response (this makes
4085        us stop sending to old Crafty's which don't understand
4086        the "." command (sending illegal cmds resets node count & time,
4087        which looks bad)) */
4088     programStats.ok_to_send = 0;
4089 }
4090
4091 void ics_update_width(new_width)
4092         int new_width;
4093 {
4094         ics_printf("set width %d\n", new_width);
4095 }
4096
4097 void
4098 SendMoveToProgram(moveNum, cps)
4099      int moveNum;
4100      ChessProgramState *cps;
4101 {
4102     char buf[MSG_SIZ];
4103
4104     if (cps->useUsermove) {
4105       SendToProgram("usermove ", cps);
4106     }
4107     if (cps->useSAN) {
4108       char *space;
4109       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4110         int len = space - parseList[moveNum];
4111         memcpy(buf, parseList[moveNum], len);
4112         buf[len++] = '\n';
4113         buf[len] = NULLCHAR;
4114       } else {
4115         sprintf(buf, "%s\n", parseList[moveNum]);
4116       }
4117       SendToProgram(buf, cps);
4118     } else {
4119       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4120         AlphaRank(moveList[moveNum], 4);
4121         SendToProgram(moveList[moveNum], cps);
4122         AlphaRank(moveList[moveNum], 4); // and back
4123       } else
4124       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4125        * the engine. It would be nice to have a better way to identify castle
4126        * moves here. */
4127       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4128                                                                          && cps->useOOCastle) {
4129         int fromX = moveList[moveNum][0] - AAA;
4130         int fromY = moveList[moveNum][1] - ONE;
4131         int toX = moveList[moveNum][2] - AAA;
4132         int toY = moveList[moveNum][3] - ONE;
4133         if((boards[moveNum][fromY][fromX] == WhiteKing
4134             && boards[moveNum][toY][toX] == WhiteRook)
4135            || (boards[moveNum][fromY][fromX] == BlackKing
4136                && boards[moveNum][toY][toX] == BlackRook)) {
4137           if(toX > fromX) SendToProgram("O-O\n", cps);
4138           else SendToProgram("O-O-O\n", cps);
4139         }
4140         else SendToProgram(moveList[moveNum], cps);
4141       }
4142       else SendToProgram(moveList[moveNum], cps);
4143       /* End of additions by Tord */
4144     }
4145
4146     /* [HGM] setting up the opening has brought engine in force mode! */
4147     /*       Send 'go' if we are in a mode where machine should play. */
4148     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4149         (gameMode == TwoMachinesPlay   ||
4150 #ifdef ZIPPY
4151          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4152 #endif
4153          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4154         SendToProgram("go\n", cps);
4155   if (appData.debugMode) {
4156     fprintf(debugFP, "(extra)\n");
4157   }
4158     }
4159     setboardSpoiledMachineBlack = 0;
4160 }
4161
4162 void
4163 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4164      ChessMove moveType;
4165      int fromX, fromY, toX, toY;
4166 {
4167     char user_move[MSG_SIZ];
4168
4169     switch (moveType) {
4170       default:
4171         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4172                 (int)moveType, fromX, fromY, toX, toY);
4173         DisplayError(user_move + strlen("say "), 0);
4174         break;
4175       case WhiteKingSideCastle:
4176       case BlackKingSideCastle:
4177       case WhiteQueenSideCastleWild:
4178       case BlackQueenSideCastleWild:
4179       /* PUSH Fabien */
4180       case WhiteHSideCastleFR:
4181       case BlackHSideCastleFR:
4182       /* POP Fabien */
4183         sprintf(user_move, "o-o\n");
4184         break;
4185       case WhiteQueenSideCastle:
4186       case BlackQueenSideCastle:
4187       case WhiteKingSideCastleWild:
4188       case BlackKingSideCastleWild:
4189       /* PUSH Fabien */
4190       case WhiteASideCastleFR:
4191       case BlackASideCastleFR:
4192       /* POP Fabien */
4193         sprintf(user_move, "o-o-o\n");
4194         break;
4195       case WhitePromotionQueen:
4196       case BlackPromotionQueen:
4197       case WhitePromotionRook:
4198       case BlackPromotionRook:
4199       case WhitePromotionBishop:
4200       case BlackPromotionBishop:
4201       case WhitePromotionKnight:
4202       case BlackPromotionKnight:
4203       case WhitePromotionKing:
4204       case BlackPromotionKing:
4205       case WhitePromotionChancellor:
4206       case BlackPromotionChancellor:
4207       case WhitePromotionArchbishop:
4208       case BlackPromotionArchbishop:
4209         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4210             sprintf(user_move, "%c%c%c%c=%c\n",
4211                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4212                 PieceToChar(WhiteFerz));
4213         else if(gameInfo.variant == VariantGreat)
4214             sprintf(user_move, "%c%c%c%c=%c\n",
4215                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4216                 PieceToChar(WhiteMan));
4217         else
4218             sprintf(user_move, "%c%c%c%c=%c\n",
4219                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4220                 PieceToChar(PromoPiece(moveType)));
4221         break;
4222       case WhiteDrop:
4223       case BlackDrop:
4224         sprintf(user_move, "%c@%c%c\n",
4225                 ToUpper(PieceToChar((ChessSquare) fromX)),
4226                 AAA + toX, ONE + toY);
4227         break;
4228       case NormalMove:
4229       case WhiteCapturesEnPassant:
4230       case BlackCapturesEnPassant:
4231       case IllegalMove:  /* could be a variant we don't quite understand */
4232         sprintf(user_move, "%c%c%c%c\n",
4233                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4234         break;
4235     }
4236     SendToICS(user_move);
4237     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4238         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4239 }
4240
4241 void
4242 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4243      int rf, ff, rt, ft;
4244      char promoChar;
4245      char move[7];
4246 {
4247     if (rf == DROP_RANK) {
4248         sprintf(move, "%c@%c%c\n",
4249                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4250     } else {
4251         if (promoChar == 'x' || promoChar == NULLCHAR) {
4252             sprintf(move, "%c%c%c%c\n",
4253                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4254         } else {
4255             sprintf(move, "%c%c%c%c%c\n",
4256                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4257         }
4258     }
4259 }
4260
4261 void
4262 ProcessICSInitScript(f)
4263      FILE *f;
4264 {
4265     char buf[MSG_SIZ];
4266
4267     while (fgets(buf, MSG_SIZ, f)) {
4268         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4269     }
4270
4271     fclose(f);
4272 }
4273
4274
4275 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4276 void
4277 AlphaRank(char *move, int n)
4278 {
4279 //    char *p = move, c; int x, y;
4280
4281     if (appData.debugMode) {
4282         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4283     }
4284
4285     if(move[1]=='*' &&
4286        move[2]>='0' && move[2]<='9' &&
4287        move[3]>='a' && move[3]<='x'    ) {
4288         move[1] = '@';
4289         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4290         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4291     } else
4292     if(move[0]>='0' && move[0]<='9' &&
4293        move[1]>='a' && move[1]<='x' &&
4294        move[2]>='0' && move[2]<='9' &&
4295        move[3]>='a' && move[3]<='x'    ) {
4296         /* input move, Shogi -> normal */
4297         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4298         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4299         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4300         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4301     } else
4302     if(move[1]=='@' &&
4303        move[3]>='0' && move[3]<='9' &&
4304        move[2]>='a' && move[2]<='x'    ) {
4305         move[1] = '*';
4306         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4307         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4308     } else
4309     if(
4310        move[0]>='a' && move[0]<='x' &&
4311        move[3]>='0' && move[3]<='9' &&
4312        move[2]>='a' && move[2]<='x'    ) {
4313          /* output move, normal -> Shogi */
4314         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4315         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4316         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4317         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4318         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4319     }
4320     if (appData.debugMode) {
4321         fprintf(debugFP, "   out = '%s'\n", move);
4322     }
4323 }
4324
4325 /* Parser for moves from gnuchess, ICS, or user typein box */
4326 Boolean
4327 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4328      char *move;
4329      int moveNum;
4330      ChessMove *moveType;
4331      int *fromX, *fromY, *toX, *toY;
4332      char *promoChar;
4333 {
4334     if (appData.debugMode) {
4335         fprintf(debugFP, "move to parse: %s\n", move);
4336     }
4337     *moveType = yylexstr(moveNum, move);
4338
4339     switch (*moveType) {
4340       case WhitePromotionChancellor:
4341       case BlackPromotionChancellor:
4342       case WhitePromotionArchbishop:
4343       case BlackPromotionArchbishop:
4344       case WhitePromotionQueen:
4345       case BlackPromotionQueen:
4346       case WhitePromotionRook:
4347       case BlackPromotionRook:
4348       case WhitePromotionBishop:
4349       case BlackPromotionBishop:
4350       case WhitePromotionKnight:
4351       case BlackPromotionKnight:
4352       case WhitePromotionKing:
4353       case BlackPromotionKing:
4354       case NormalMove:
4355       case WhiteCapturesEnPassant:
4356       case BlackCapturesEnPassant:
4357       case WhiteKingSideCastle:
4358       case WhiteQueenSideCastle:
4359       case BlackKingSideCastle:
4360       case BlackQueenSideCastle:
4361       case WhiteKingSideCastleWild:
4362       case WhiteQueenSideCastleWild:
4363       case BlackKingSideCastleWild:
4364       case BlackQueenSideCastleWild:
4365       /* Code added by Tord: */
4366       case WhiteHSideCastleFR:
4367       case WhiteASideCastleFR:
4368       case BlackHSideCastleFR:
4369       case BlackASideCastleFR:
4370       /* End of code added by Tord */
4371       case IllegalMove:         /* bug or odd chess variant */
4372         *fromX = currentMoveString[0] - AAA;
4373         *fromY = currentMoveString[1] - ONE;
4374         *toX = currentMoveString[2] - AAA;
4375         *toY = currentMoveString[3] - ONE;
4376         *promoChar = currentMoveString[4];
4377         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4378             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4379     if (appData.debugMode) {
4380         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4381     }
4382             *fromX = *fromY = *toX = *toY = 0;
4383             return FALSE;
4384         }
4385         if (appData.testLegality) {
4386           return (*moveType != IllegalMove);
4387         } else {
4388           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4389                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4390         }
4391
4392       case WhiteDrop:
4393       case BlackDrop:
4394         *fromX = *moveType == WhiteDrop ?
4395           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4396           (int) CharToPiece(ToLower(currentMoveString[0]));
4397         *fromY = DROP_RANK;
4398         *toX = currentMoveString[2] - AAA;
4399         *toY = currentMoveString[3] - ONE;
4400         *promoChar = NULLCHAR;
4401         return TRUE;
4402
4403       case AmbiguousMove:
4404       case ImpossibleMove:
4405       case (ChessMove) 0:       /* end of file */
4406       case ElapsedTime:
4407       case Comment:
4408       case PGNTag:
4409       case NAG:
4410       case WhiteWins:
4411       case BlackWins:
4412       case GameIsDrawn:
4413       default:
4414     if (appData.debugMode) {
4415         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4416     }
4417         /* bug? */
4418         *fromX = *fromY = *toX = *toY = 0;
4419         *promoChar = NULLCHAR;
4420         return FALSE;
4421     }
4422 }
4423
4424
4425 void
4426 ParsePV(char *pv)
4427 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4428   int fromX, fromY, toX, toY; char promoChar;
4429   ChessMove moveType;
4430   Boolean valid;
4431   int nr = 0;
4432
4433   endPV = forwardMostMove;
4434   do {
4435     while(*pv == ' ') pv++;
4436     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4437     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4438 if(appData.debugMode){
4439 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4440 }
4441     if(!valid && nr == 0 &&
4442        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4443         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4444     }
4445     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4446     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4447     nr++;
4448     if(endPV+1 > framePtr) break; // no space, truncate
4449     if(!valid) break;
4450     endPV++;
4451     CopyBoard(boards[endPV], boards[endPV-1]);
4452     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4453     moveList[endPV-1][0] = fromX + AAA;
4454     moveList[endPV-1][1] = fromY + ONE;
4455     moveList[endPV-1][2] = toX + AAA;
4456     moveList[endPV-1][3] = toY + ONE;
4457     parseList[endPV-1][0] = NULLCHAR;
4458   } while(valid);
4459   currentMove = endPV;
4460   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4461   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4462                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4463   DrawPosition(TRUE, boards[currentMove]);
4464 }
4465
4466 static int lastX, lastY;
4467
4468 Boolean
4469 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4470 {
4471         int startPV;
4472
4473         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4474         lastX = x; lastY = y;
4475         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4476         startPV = index;
4477       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4478       index = startPV;
4479         while(buf[index] && buf[index] != '\n') index++;
4480         buf[index] = 0;
4481         ParsePV(buf+startPV);
4482         *start = startPV; *end = index-1;
4483         return TRUE;
4484 }
4485
4486 Boolean
4487 LoadPV(int x, int y)
4488 { // called on right mouse click to load PV
4489   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4490   lastX = x; lastY = y;
4491   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4492   return TRUE;
4493 }
4494
4495 void
4496 UnLoadPV()
4497 {
4498   if(endPV < 0) return;
4499   endPV = -1;
4500   currentMove = forwardMostMove;
4501   ClearPremoveHighlights();
4502   DrawPosition(TRUE, boards[currentMove]);
4503 }
4504
4505 void
4506 MovePV(int x, int y, int h)
4507 { // step through PV based on mouse coordinates (called on mouse move)
4508   int margin = h>>3, step = 0;
4509
4510   if(endPV < 0) return;
4511   // we must somehow check if right button is still down (might be released off board!)
4512   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4513   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4514   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4515   if(!step) return;
4516   lastX = x; lastY = y;
4517   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4518   currentMove += step;
4519   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4520   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4521                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4522   DrawPosition(FALSE, boards[currentMove]);
4523 }
4524
4525
4526 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4527 // All positions will have equal probability, but the current method will not provide a unique
4528 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4529 #define DARK 1
4530 #define LITE 2
4531 #define ANY 3
4532
4533 int squaresLeft[4];
4534 int piecesLeft[(int)BlackPawn];
4535 int seed, nrOfShuffles;
4536
4537 void GetPositionNumber()
4538 {       // sets global variable seed
4539         int i;
4540
4541         seed = appData.defaultFrcPosition;
4542         if(seed < 0) { // randomize based on time for negative FRC position numbers
4543                 for(i=0; i<50; i++) seed += random();
4544                 seed = random() ^ random() >> 8 ^ random() << 8;
4545                 if(seed<0) seed = -seed;
4546         }
4547 }
4548
4549 int put(Board board, int pieceType, int rank, int n, int shade)
4550 // put the piece on the (n-1)-th empty squares of the given shade
4551 {
4552         int i;
4553
4554         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4555                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4556                         board[rank][i] = (ChessSquare) pieceType;
4557                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4558                         squaresLeft[ANY]--;
4559                         piecesLeft[pieceType]--;
4560                         return i;
4561                 }
4562         }
4563         return -1;
4564 }
4565
4566
4567 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4568 // calculate where the next piece goes, (any empty square), and put it there
4569 {
4570         int i;
4571
4572         i = seed % squaresLeft[shade];
4573         nrOfShuffles *= squaresLeft[shade];
4574         seed /= squaresLeft[shade];
4575         put(board, pieceType, rank, i, shade);
4576 }
4577
4578 void AddTwoPieces(Board board, int pieceType, int rank)
4579 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4580 {
4581         int i, n=squaresLeft[ANY], j=n-1, k;
4582
4583         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4584         i = seed % k;  // pick one
4585         nrOfShuffles *= k;
4586         seed /= k;
4587         while(i >= j) i -= j--;
4588         j = n - 1 - j; i += j;
4589         put(board, pieceType, rank, j, ANY);
4590         put(board, pieceType, rank, i, ANY);
4591 }
4592
4593 void SetUpShuffle(Board board, int number)
4594 {
4595         int i, p, first=1;
4596
4597         GetPositionNumber(); nrOfShuffles = 1;
4598
4599         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4600         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4601         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4602
4603         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4604
4605         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4606             p = (int) board[0][i];
4607             if(p < (int) BlackPawn) piecesLeft[p] ++;
4608             board[0][i] = EmptySquare;
4609         }
4610
4611         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4612             // shuffles restricted to allow normal castling put KRR first
4613             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4614                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4615             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4616                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4617             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4618                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4619             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4620                 put(board, WhiteRook, 0, 0, ANY);
4621             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4622         }
4623
4624         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4625             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4626             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4627                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4628                 while(piecesLeft[p] >= 2) {
4629                     AddOnePiece(board, p, 0, LITE);
4630                     AddOnePiece(board, p, 0, DARK);
4631                 }
4632                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4633             }
4634
4635         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4636             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4637             // but we leave King and Rooks for last, to possibly obey FRC restriction
4638             if(p == (int)WhiteRook) continue;
4639             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4640             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4641         }
4642
4643         // now everything is placed, except perhaps King (Unicorn) and Rooks
4644
4645         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4646             // Last King gets castling rights
4647             while(piecesLeft[(int)WhiteUnicorn]) {
4648                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4649                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4650             }
4651
4652             while(piecesLeft[(int)WhiteKing]) {
4653                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4654                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4655             }
4656
4657
4658         } else {
4659             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4660             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4661         }
4662
4663         // Only Rooks can be left; simply place them all
4664         while(piecesLeft[(int)WhiteRook]) {
4665                 i = put(board, WhiteRook, 0, 0, ANY);
4666                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4667                         if(first) {
4668                                 first=0;
4669                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4670                         }
4671                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4672                 }
4673         }
4674         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4675             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4676         }
4677
4678         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4679 }
4680
4681 int SetCharTable( char *table, const char * map )
4682 /* [HGM] moved here from winboard.c because of its general usefulness */
4683 /*       Basically a safe strcpy that uses the last character as King */
4684 {
4685     int result = FALSE; int NrPieces;
4686
4687     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4688                     && NrPieces >= 12 && !(NrPieces&1)) {
4689         int i; /* [HGM] Accept even length from 12 to 34 */
4690
4691         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4692         for( i=0; i<NrPieces/2-1; i++ ) {
4693             table[i] = map[i];
4694             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4695         }
4696         table[(int) WhiteKing]  = map[NrPieces/2-1];
4697         table[(int) BlackKing]  = map[NrPieces-1];
4698
4699         result = TRUE;
4700     }
4701
4702     return result;
4703 }
4704
4705 void Prelude(Board board)
4706 {       // [HGM] superchess: random selection of exo-pieces
4707         int i, j, k; ChessSquare p;
4708         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4709
4710         GetPositionNumber(); // use FRC position number
4711
4712         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4713             SetCharTable(pieceToChar, appData.pieceToCharTable);
4714             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4715                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4716         }
4717
4718         j = seed%4;                 seed /= 4;
4719         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4720         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4721         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4722         j = seed%3 + (seed%3 >= j); seed /= 3;
4723         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4724         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4725         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4726         j = seed%3;                 seed /= 3;
4727         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4728         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4729         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4730         j = seed%2 + (seed%2 >= j); seed /= 2;
4731         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4732         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4733         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4734         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4735         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4736         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4737         put(board, exoPieces[0],    0, 0, ANY);
4738         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4739 }
4740
4741 void
4742 InitPosition(redraw)
4743      int redraw;
4744 {
4745     ChessSquare (* pieces)[BOARD_FILES];
4746     int i, j, pawnRow, overrule,
4747     oldx = gameInfo.boardWidth,
4748     oldy = gameInfo.boardHeight,
4749     oldh = gameInfo.holdingsWidth,
4750     oldv = gameInfo.variant;
4751
4752     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4753
4754     /* [AS] Initialize pv info list [HGM] and game status */
4755     {
4756         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4757             pvInfoList[i].depth = 0;
4758             boards[i][EP_STATUS] = EP_NONE;
4759             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4760         }
4761
4762         initialRulePlies = 0; /* 50-move counter start */
4763
4764         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4765         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4766     }
4767
4768
4769     /* [HGM] logic here is completely changed. In stead of full positions */
4770     /* the initialized data only consist of the two backranks. The switch */
4771     /* selects which one we will use, which is than copied to the Board   */
4772     /* initialPosition, which for the rest is initialized by Pawns and    */
4773     /* empty squares. This initial position is then copied to boards[0],  */
4774     /* possibly after shuffling, so that it remains available.            */
4775
4776     gameInfo.holdingsWidth = 0; /* default board sizes */
4777     gameInfo.boardWidth    = 8;
4778     gameInfo.boardHeight   = 8;
4779     gameInfo.holdingsSize  = 0;
4780     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4781     for(i=0; i<BOARD_FILES-2; i++)
4782       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4783     initialPosition[EP_STATUS] = EP_NONE;
4784     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4785
4786     switch (gameInfo.variant) {
4787     case VariantFischeRandom:
4788       shuffleOpenings = TRUE;
4789     default:
4790       pieces = FIDEArray;
4791       break;
4792     case VariantShatranj:
4793       pieces = ShatranjArray;
4794       nrCastlingRights = 0;
4795       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4796       break;
4797     case VariantMakruk:
4798       pieces = makrukArray;
4799       nrCastlingRights = 0;
4800       startedFromSetupPosition = TRUE;
4801       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4802       break;
4803     case VariantTwoKings:
4804       pieces = twoKingsArray;
4805       break;
4806     case VariantCapaRandom:
4807       shuffleOpenings = TRUE;
4808     case VariantCapablanca:
4809       pieces = CapablancaArray;
4810       gameInfo.boardWidth = 10;
4811       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4812       break;
4813     case VariantGothic:
4814       pieces = GothicArray;
4815       gameInfo.boardWidth = 10;
4816       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4817       break;
4818     case VariantJanus:
4819       pieces = JanusArray;
4820       gameInfo.boardWidth = 10;
4821       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4822       nrCastlingRights = 6;
4823         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4824         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4825         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4826         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4827         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4828         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4829       break;
4830     case VariantFalcon:
4831       pieces = FalconArray;
4832       gameInfo.boardWidth = 10;
4833       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4834       break;
4835     case VariantXiangqi:
4836       pieces = XiangqiArray;
4837       gameInfo.boardWidth  = 9;
4838       gameInfo.boardHeight = 10;
4839       nrCastlingRights = 0;
4840       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4841       break;
4842     case VariantShogi:
4843       pieces = ShogiArray;
4844       gameInfo.boardWidth  = 9;
4845       gameInfo.boardHeight = 9;
4846       gameInfo.holdingsSize = 7;
4847       nrCastlingRights = 0;
4848       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4849       break;
4850     case VariantCourier:
4851       pieces = CourierArray;
4852       gameInfo.boardWidth  = 12;
4853       nrCastlingRights = 0;
4854       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4855       break;
4856     case VariantKnightmate:
4857       pieces = KnightmateArray;
4858       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4859       break;
4860     case VariantFairy:
4861       pieces = fairyArray;
4862       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4863       break;
4864     case VariantGreat:
4865       pieces = GreatArray;
4866       gameInfo.boardWidth = 10;
4867       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4868       gameInfo.holdingsSize = 8;
4869       break;
4870     case VariantSuper:
4871       pieces = FIDEArray;
4872       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4873       gameInfo.holdingsSize = 8;
4874       startedFromSetupPosition = TRUE;
4875       break;
4876     case VariantCrazyhouse:
4877     case VariantBughouse:
4878       pieces = FIDEArray;
4879       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4880       gameInfo.holdingsSize = 5;
4881       break;
4882     case VariantWildCastle:
4883       pieces = FIDEArray;
4884       /* !!?shuffle with kings guaranteed to be on d or e file */
4885       shuffleOpenings = 1;
4886       break;
4887     case VariantNoCastle:
4888       pieces = FIDEArray;
4889       nrCastlingRights = 0;
4890       /* !!?unconstrained back-rank shuffle */
4891       shuffleOpenings = 1;
4892       break;
4893     }
4894
4895     overrule = 0;
4896     if(appData.NrFiles >= 0) {
4897         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4898         gameInfo.boardWidth = appData.NrFiles;
4899     }
4900     if(appData.NrRanks >= 0) {
4901         gameInfo.boardHeight = appData.NrRanks;
4902     }
4903     if(appData.holdingsSize >= 0) {
4904         i = appData.holdingsSize;
4905         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4906         gameInfo.holdingsSize = i;
4907     }
4908     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4909     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4910         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4911
4912     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4913     if(pawnRow < 1) pawnRow = 1;
4914     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4915
4916     /* User pieceToChar list overrules defaults */
4917     if(appData.pieceToCharTable != NULL)
4918         SetCharTable(pieceToChar, appData.pieceToCharTable);
4919
4920     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4921
4922         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4923             s = (ChessSquare) 0; /* account holding counts in guard band */
4924         for( i=0; i<BOARD_HEIGHT; i++ )
4925             initialPosition[i][j] = s;
4926
4927         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4928         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4929         initialPosition[pawnRow][j] = WhitePawn;
4930         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4931         if(gameInfo.variant == VariantXiangqi) {
4932             if(j&1) {
4933                 initialPosition[pawnRow][j] =
4934                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4935                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4936                    initialPosition[2][j] = WhiteCannon;
4937                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4938                 }
4939             }
4940         }
4941         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4942     }
4943     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4944
4945             j=BOARD_LEFT+1;
4946             initialPosition[1][j] = WhiteBishop;
4947             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4948             j=BOARD_RGHT-2;
4949             initialPosition[1][j] = WhiteRook;
4950             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4951     }
4952
4953     if( nrCastlingRights == -1) {
4954         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4955         /*       This sets default castling rights from none to normal corners   */
4956         /* Variants with other castling rights must set them themselves above    */
4957         nrCastlingRights = 6;
4958         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4959         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4960         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4961         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4962         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4963         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4964      }
4965
4966      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4967      if(gameInfo.variant == VariantGreat) { // promotion commoners
4968         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4969         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4970         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4971         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4972      }
4973   if (appData.debugMode) {
4974     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4975   }
4976     if(shuffleOpenings) {
4977         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4978         startedFromSetupPosition = TRUE;
4979     }
4980     if(startedFromPositionFile) {
4981       /* [HGM] loadPos: use PositionFile for every new game */
4982       CopyBoard(initialPosition, filePosition);
4983       for(i=0; i<nrCastlingRights; i++)
4984           initialRights[i] = filePosition[CASTLING][i];
4985       startedFromSetupPosition = TRUE;
4986     }
4987
4988     CopyBoard(boards[0], initialPosition);
4989     if(oldx != gameInfo.boardWidth ||
4990        oldy != gameInfo.boardHeight ||
4991        oldh != gameInfo.holdingsWidth
4992 #ifdef GOTHIC
4993        || oldv == VariantGothic ||        // For licensing popups
4994        gameInfo.variant == VariantGothic
4995 #endif
4996 #ifdef FALCON
4997        || oldv == VariantFalcon ||
4998        gameInfo.variant == VariantFalcon
4999 #endif
5000                                          )
5001       {
5002             InitDrawingSizes(-2 ,0);
5003       }
5004
5005     if (redraw)
5006       DrawPosition(TRUE, boards[currentMove]);
5007
5008 }
5009
5010 void
5011 SendBoard(cps, moveNum)
5012      ChessProgramState *cps;
5013      int moveNum;
5014 {
5015     char message[MSG_SIZ];
5016
5017     if (cps->useSetboard) {
5018       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5019       sprintf(message, "setboard %s\n", fen);
5020       SendToProgram(message, cps);
5021       free(fen);
5022
5023     } else {
5024       ChessSquare *bp;
5025       int i, j;
5026       /* Kludge to set black to move, avoiding the troublesome and now
5027        * deprecated "black" command.
5028        */
5029       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5030
5031       SendToProgram("edit\n", cps);
5032       SendToProgram("#\n", cps);
5033       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5034         bp = &boards[moveNum][i][BOARD_LEFT];
5035         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5036           if ((int) *bp < (int) BlackPawn) {
5037             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5038                     AAA + j, ONE + i);
5039             if(message[0] == '+' || message[0] == '~') {
5040                 sprintf(message, "%c%c%c+\n",
5041                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5042                         AAA + j, ONE + i);
5043             }
5044             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5045                 message[1] = BOARD_RGHT   - 1 - j + '1';
5046                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5047             }
5048             SendToProgram(message, cps);
5049           }
5050         }
5051       }
5052
5053       SendToProgram("c\n", cps);
5054       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5055         bp = &boards[moveNum][i][BOARD_LEFT];
5056         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5057           if (((int) *bp != (int) EmptySquare)
5058               && ((int) *bp >= (int) BlackPawn)) {
5059             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5060                     AAA + j, ONE + i);
5061             if(message[0] == '+' || message[0] == '~') {
5062                 sprintf(message, "%c%c%c+\n",
5063                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5064                         AAA + j, ONE + i);
5065             }
5066             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5067                 message[1] = BOARD_RGHT   - 1 - j + '1';
5068                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5069             }
5070             SendToProgram(message, cps);
5071           }
5072         }
5073       }
5074
5075       SendToProgram(".\n", cps);
5076     }
5077     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5078 }
5079
5080 int
5081 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5082 {
5083     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5084     /* [HGM] add Shogi promotions */
5085     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5086     ChessSquare piece;
5087     ChessMove moveType;
5088     Boolean premove;
5089
5090     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5091     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5092
5093     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5094       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5095         return FALSE;
5096
5097     piece = boards[currentMove][fromY][fromX];
5098     if(gameInfo.variant == VariantShogi) {
5099         promotionZoneSize = 3;
5100         highestPromotingPiece = (int)WhiteFerz;
5101     } else if(gameInfo.variant == VariantMakruk) {
5102         promotionZoneSize = 3;
5103     }
5104
5105     // next weed out all moves that do not touch the promotion zone at all
5106     if((int)piece >= BlackPawn) {
5107         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5108              return FALSE;
5109         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5110     } else {
5111         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5112            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5113     }
5114
5115     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5116
5117     // weed out mandatory Shogi promotions
5118     if(gameInfo.variant == VariantShogi) {
5119         if(piece >= BlackPawn) {
5120             if(toY == 0 && piece == BlackPawn ||
5121                toY == 0 && piece == BlackQueen ||
5122                toY <= 1 && piece == BlackKnight) {
5123                 *promoChoice = '+';
5124                 return FALSE;
5125             }
5126         } else {
5127             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5128                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5129                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5130                 *promoChoice = '+';
5131                 return FALSE;
5132             }
5133         }
5134     }
5135
5136     // weed out obviously illegal Pawn moves
5137     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5138         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5139         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5140         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5141         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5142         // note we are not allowed to test for valid (non-)capture, due to premove
5143     }
5144
5145     // we either have a choice what to promote to, or (in Shogi) whether to promote
5146     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5147         *promoChoice = PieceToChar(BlackFerz);  // no choice
5148         return FALSE;
5149     }
5150     if(appData.alwaysPromoteToQueen) { // predetermined
5151         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5152              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5153         else *promoChoice = PieceToChar(BlackQueen);
5154         return FALSE;
5155     }
5156
5157     // suppress promotion popup on illegal moves that are not premoves
5158     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5159               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5160     if(appData.testLegality && !premove) {
5161         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5162                         fromY, fromX, toY, toX, NULLCHAR);
5163         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5164            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5165             return FALSE;
5166     }
5167
5168     return TRUE;
5169 }
5170
5171 int
5172 InPalace(row, column)
5173      int row, column;
5174 {   /* [HGM] for Xiangqi */
5175     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5176          column < (BOARD_WIDTH + 4)/2 &&
5177          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5178     return FALSE;
5179 }
5180
5181 int
5182 PieceForSquare (x, y)
5183      int x;
5184      int y;
5185 {
5186   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5187      return -1;
5188   else
5189      return boards[currentMove][y][x];
5190 }
5191
5192 int
5193 OKToStartUserMove(x, y)
5194      int x, y;
5195 {
5196     ChessSquare from_piece;
5197     int white_piece;
5198
5199     if (matchMode) return FALSE;
5200     if (gameMode == EditPosition) return TRUE;
5201
5202     if (x >= 0 && y >= 0)
5203       from_piece = boards[currentMove][y][x];
5204     else
5205       from_piece = EmptySquare;
5206
5207     if (from_piece == EmptySquare) return FALSE;
5208
5209     white_piece = (int)from_piece >= (int)WhitePawn &&
5210       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5211
5212     switch (gameMode) {
5213       case PlayFromGameFile:
5214       case AnalyzeFile:
5215       case TwoMachinesPlay:
5216       case EndOfGame:
5217         return FALSE;
5218
5219       case IcsObserving:
5220       case IcsIdle:
5221         return FALSE;
5222
5223       case MachinePlaysWhite:
5224       case IcsPlayingBlack:
5225         if (appData.zippyPlay) return FALSE;
5226         if (white_piece) {
5227             DisplayMoveError(_("You are playing Black"));
5228             return FALSE;
5229         }
5230         break;
5231
5232       case MachinePlaysBlack:
5233       case IcsPlayingWhite:
5234         if (appData.zippyPlay) return FALSE;
5235         if (!white_piece) {
5236             DisplayMoveError(_("You are playing White"));
5237             return FALSE;
5238         }
5239         break;
5240
5241       case EditGame:
5242         if (!white_piece && WhiteOnMove(currentMove)) {
5243             DisplayMoveError(_("It is White's turn"));
5244             return FALSE;
5245         }
5246         if (white_piece && !WhiteOnMove(currentMove)) {
5247             DisplayMoveError(_("It is Black's turn"));
5248             return FALSE;
5249         }
5250         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5251             /* Editing correspondence game history */
5252             /* Could disallow this or prompt for confirmation */
5253             cmailOldMove = -1;
5254         }
5255         break;
5256
5257       case BeginningOfGame:
5258         if (appData.icsActive) return FALSE;
5259         if (!appData.noChessProgram) {
5260             if (!white_piece) {
5261                 DisplayMoveError(_("You are playing White"));
5262                 return FALSE;
5263             }
5264         }
5265         break;
5266
5267       case Training:
5268         if (!white_piece && WhiteOnMove(currentMove)) {
5269             DisplayMoveError(_("It is White's turn"));
5270             return FALSE;
5271         }
5272         if (white_piece && !WhiteOnMove(currentMove)) {
5273             DisplayMoveError(_("It is Black's turn"));
5274             return FALSE;
5275         }
5276         break;
5277
5278       default:
5279       case IcsExamining:
5280         break;
5281     }
5282     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5283         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5284         && gameMode != AnalyzeFile && gameMode != Training) {
5285         DisplayMoveError(_("Displayed position is not current"));
5286         return FALSE;
5287     }
5288     return TRUE;
5289 }
5290
5291 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5292 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5293 int lastLoadGameUseList = FALSE;
5294 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5295 ChessMove lastLoadGameStart = (ChessMove) 0;
5296
5297 ChessMove
5298 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5299      int fromX, fromY, toX, toY;
5300      int promoChar;
5301      Boolean captureOwn;
5302 {
5303     ChessMove moveType;
5304     ChessSquare pdown, pup;
5305
5306     /* Check if the user is playing in turn.  This is complicated because we
5307        let the user "pick up" a piece before it is his turn.  So the piece he
5308        tried to pick up may have been captured by the time he puts it down!
5309        Therefore we use the color the user is supposed to be playing in this
5310        test, not the color of the piece that is currently on the starting
5311        square---except in EditGame mode, where the user is playing both
5312        sides; fortunately there the capture race can't happen.  (It can
5313        now happen in IcsExamining mode, but that's just too bad.  The user
5314        will get a somewhat confusing message in that case.)
5315        */
5316
5317     switch (gameMode) {
5318       case PlayFromGameFile:
5319       case AnalyzeFile:
5320       case TwoMachinesPlay:
5321       case EndOfGame:
5322       case IcsObserving:
5323       case IcsIdle:
5324         /* We switched into a game mode where moves are not accepted,
5325            perhaps while the mouse button was down. */
5326         return ImpossibleMove;
5327
5328       case MachinePlaysWhite:
5329         /* User is moving for Black */
5330         if (WhiteOnMove(currentMove)) {
5331             DisplayMoveError(_("It is White's turn"));
5332             return ImpossibleMove;
5333         }
5334         break;
5335
5336       case MachinePlaysBlack:
5337         /* User is moving for White */
5338         if (!WhiteOnMove(currentMove)) {
5339             DisplayMoveError(_("It is Black's turn"));
5340             return ImpossibleMove;
5341         }
5342         break;
5343
5344       case EditGame:
5345       case IcsExamining:
5346       case BeginningOfGame:
5347       case AnalyzeMode:
5348       case Training:
5349         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5350             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5351             /* User is moving for Black */
5352             if (WhiteOnMove(currentMove)) {
5353                 DisplayMoveError(_("It is White's turn"));
5354                 return ImpossibleMove;
5355             }
5356         } else {
5357             /* User is moving for White */
5358             if (!WhiteOnMove(currentMove)) {
5359                 DisplayMoveError(_("It is Black's turn"));
5360                 return ImpossibleMove;
5361             }
5362         }
5363         break;
5364
5365       case IcsPlayingBlack:
5366         /* User is moving for Black */
5367         if (WhiteOnMove(currentMove)) {
5368             if (!appData.premove) {
5369                 DisplayMoveError(_("It is White's turn"));
5370             } else if (toX >= 0 && toY >= 0) {
5371                 premoveToX = toX;
5372                 premoveToY = toY;
5373                 premoveFromX = fromX;
5374                 premoveFromY = fromY;
5375                 premovePromoChar = promoChar;
5376                 gotPremove = 1;
5377                 if (appData.debugMode)
5378                     fprintf(debugFP, "Got premove: fromX %d,"
5379                             "fromY %d, toX %d, toY %d\n",
5380                             fromX, fromY, toX, toY);
5381             }
5382             return ImpossibleMove;
5383         }
5384         break;
5385
5386       case IcsPlayingWhite:
5387         /* User is moving for White */
5388         if (!WhiteOnMove(currentMove)) {
5389             if (!appData.premove) {
5390                 DisplayMoveError(_("It is Black's turn"));
5391             } else if (toX >= 0 && toY >= 0) {
5392                 premoveToX = toX;
5393                 premoveToY = toY;
5394                 premoveFromX = fromX;
5395                 premoveFromY = fromY;
5396                 premovePromoChar = promoChar;
5397                 gotPremove = 1;
5398                 if (appData.debugMode)
5399                     fprintf(debugFP, "Got premove: fromX %d,"
5400                             "fromY %d, toX %d, toY %d\n",
5401                             fromX, fromY, toX, toY);
5402             }
5403             return ImpossibleMove;
5404         }
5405         break;
5406
5407       default:
5408         break;
5409
5410       case EditPosition:
5411         /* EditPosition, empty square, or different color piece;
5412            click-click move is possible */
5413         if (toX == -2 || toY == -2) {
5414             boards[0][fromY][fromX] = EmptySquare;
5415             return AmbiguousMove;
5416         } else if (toX >= 0 && toY >= 0) {
5417             boards[0][toY][toX] = boards[0][fromY][fromX];
5418             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5419                 if(boards[0][fromY][0] != EmptySquare) {
5420                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5421                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5422                 }
5423             } else
5424             if(fromX == BOARD_RGHT+1) {
5425                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5426                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5427                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5428                 }
5429             } else
5430             boards[0][fromY][fromX] = EmptySquare;
5431             return AmbiguousMove;
5432         }
5433         return ImpossibleMove;
5434     }
5435
5436     if(toX < 0 || toY < 0) return ImpossibleMove;
5437     pdown = boards[currentMove][fromY][fromX];
5438     pup = boards[currentMove][toY][toX];
5439
5440     /* [HGM] If move started in holdings, it means a drop */
5441     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5442          if( pup != EmptySquare ) return ImpossibleMove;
5443          if(appData.testLegality) {
5444              /* it would be more logical if LegalityTest() also figured out
5445               * which drops are legal. For now we forbid pawns on back rank.
5446               * Shogi is on its own here...
5447               */
5448              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5449                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5450                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5451          }
5452          return WhiteDrop; /* Not needed to specify white or black yet */
5453     }
5454
5455     userOfferedDraw = FALSE;
5456
5457     /* [HGM] always test for legality, to get promotion info */
5458     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5459                                          fromY, fromX, toY, toX, promoChar);
5460     /* [HGM] but possibly ignore an IllegalMove result */
5461     if (appData.testLegality) {
5462         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5463             DisplayMoveError(_("Illegal move"));
5464             return ImpossibleMove;
5465         }
5466     }
5467
5468     return moveType;
5469     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5470        function is made into one that returns an OK move type if FinishMove
5471        should be called. This to give the calling driver routine the
5472        opportunity to finish the userMove input with a promotion popup,
5473        without bothering the user with this for invalid or illegal moves */
5474
5475 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5476 }
5477
5478 /* Common tail of UserMoveEvent and DropMenuEvent */
5479 int
5480 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5481      ChessMove moveType;
5482      int fromX, fromY, toX, toY;
5483      /*char*/int promoChar;
5484 {
5485   char *bookHit = 0;
5486
5487   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5488     {
5489       // [HGM] superchess: suppress promotions to non-available piece
5490       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5491       if(WhiteOnMove(currentMove))
5492         {
5493           if(!boards[currentMove][k][BOARD_WIDTH-2])
5494             return 0;
5495         }
5496       else
5497         {
5498           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5499             return 0;
5500         }
5501     }
5502   
5503   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5504      move type in caller when we know the move is a legal promotion */
5505   if(moveType == NormalMove && promoChar)
5506     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5507   
5508   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5509      move type in caller when we know the move is a legal promotion */
5510   if(moveType == NormalMove && promoChar)
5511     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5512   
5513   /* [HGM] convert drag-and-drop piece drops to standard form */
5514   if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5515     {
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     {
5532       /* compare the move played on the board to the next move in the
5533        * game. If they match, display the move and the opponent's response.
5534        * If they don't match, display an error message.
5535        */
5536       int saveAnimate;
5537       Board testBoard;
5538       CopyBoard(testBoard, boards[currentMove]);
5539       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5540
5541       if (CompareBoards(testBoard, boards[currentMove+1]))
5542         {
5543           ForwardInner(currentMove+1);
5544
5545           /* Autoplay the opponent's response.
5546            * if appData.animate was TRUE when Training mode was entered,
5547            * the response will be animated.
5548            */
5549           saveAnimate = appData.animate;
5550           appData.animate = animateTraining;
5551           ForwardInner(currentMove+1);
5552           appData.animate = saveAnimate;
5553
5554           /* check for the end of the game */
5555           if (currentMove >= forwardMostMove)
5556             {
5557               gameMode = PlayFromGameFile;
5558               ModeHighlight();
5559               SetTrainingModeOff();
5560               DisplayInformation(_("End of game"));
5561             }
5562         }
5563       else
5564         {
5565           DisplayError(_("Incorrect move"), 0);
5566         }
5567       return 1;
5568     }
5569
5570   /* Ok, now we know that the move is good, so we can kill
5571      the previous line in Analysis Mode */
5572   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5573                                 && currentMove < forwardMostMove) {
5574     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5575   }
5576
5577   /* If we need the chess program but it's dead, restart it */
5578   ResurrectChessProgram();
5579
5580   /* A user move restarts a paused game*/
5581   if (pausing)
5582     PauseEvent();
5583
5584   thinkOutput[0] = NULLCHAR;
5585
5586   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5587
5588   if (gameMode == BeginningOfGame)
5589     {
5590       if (appData.noChessProgram)
5591         {
5592           gameMode = EditGame;
5593           SetGameInfo();
5594         }
5595       else
5596         {
5597           char buf[MSG_SIZ];
5598           gameMode = MachinePlaysBlack;
5599           StartClocks();
5600           SetGameInfo();
5601           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5602           DisplayTitle(buf);
5603           if (first.sendName)
5604             {
5605               sprintf(buf, "name %s\n", gameInfo.white);
5606               SendToProgram(buf, &first);
5607             }
5608           StartClocks();
5609         }
5610       ModeHighlight();
5611     }
5612
5613   /* Relay move to ICS or chess engine */
5614   if (appData.icsActive)
5615     {
5616       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5617           gameMode == IcsExamining)
5618         {
5619           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5620           ics_user_moved = 1;
5621         }
5622     }
5623   else
5624     {
5625       if (first.sendTime && (gameMode == BeginningOfGame ||
5626                              gameMode == MachinePlaysWhite ||
5627                              gameMode == MachinePlaysBlack))
5628         {
5629           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5630         }
5631       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5632         {
5633           // [HGM] book: if program might be playing, let it use book
5634           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5635           first.maybeThinking = TRUE;
5636         }
5637       else
5638         SendMoveToProgram(forwardMostMove-1, &first);
5639       if (currentMove == cmailOldMove + 1)
5640         {
5641           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5642         }
5643     }
5644
5645   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5646
5647   switch (gameMode) 
5648     {
5649     case EditGame:
5650       switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) 
5651         {
5652         case MT_NONE:
5653         case MT_CHECK:
5654           break;
5655         case MT_CHECKMATE:
5656         case MT_STAINMATE:
5657           if (WhiteOnMove(currentMove)) {
5658             GameEnds(BlackWins, "Black mates", GE_PLAYER);
5659           } else {
5660             GameEnds(WhiteWins, "White mates", GE_PLAYER);
5661           }
5662           break;
5663         case MT_STALEMATE:
5664           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5665           break;
5666         }
5667       break;
5668       
5669     case MachinePlaysBlack:
5670     case MachinePlaysWhite:
5671       /* disable certain menu options while machine is thinking */
5672       SetMachineThinkingEnables();
5673       break;
5674       
5675     default:
5676       break;
5677     }
5678   
5679   if(bookHit)
5680     { // [HGM] book: simulate book reply
5681       static char bookMove[MSG_SIZ]; // a bit generous?
5682
5683       programStats.nodes = programStats.depth = programStats.time =
5684         programStats.score = programStats.got_only_move = 0;
5685       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5686
5687       strcpy(bookMove, "move ");
5688       strcat(bookMove, bookHit);
5689       HandleMachineMove(bookMove, &first);
5690     }
5691
5692   return 1;
5693 }
5694
5695 void
5696 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5697      int fromX, fromY, toX, toY;
5698      int promoChar;
5699 {
5700     /* [HGM] This routine was added to allow calling of its two logical
5701        parts from other modules in the old way. Before, UserMoveEvent()
5702        automatically called FinishMove() if the move was OK, and returned
5703        otherwise. I separated the two, in order to make it possible to
5704        slip a promotion popup in between. But that it always needs two
5705        calls, to the first part, (now called UserMoveTest() ), and to
5706        FinishMove if the first part succeeded. Calls that do not need
5707        to do anything in between, can call this routine the old way.
5708     */
5709   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5710   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5711   if(moveType == AmbiguousMove)
5712     DrawPosition(FALSE, boards[currentMove]);
5713   else if(moveType != ImpossibleMove && moveType != Comment)
5714     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5715 }
5716
5717 void
5718 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5719      Board board;
5720      int flags;
5721      ChessMove kind;
5722      int rf, ff, rt, ft;
5723      VOIDSTAR closure;
5724 {
5725     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5726     Markers *m = (Markers *) closure;
5727     if(rf == fromY && ff == fromX)
5728         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5729                          || kind == WhiteCapturesEnPassant
5730                          || kind == BlackCapturesEnPassant);
5731     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5732 }
5733
5734 void
5735 MarkTargetSquares(int clear)
5736 {
5737   int x, y;
5738   if(!appData.markers || !appData.highlightDragging || 
5739      !appData.testLegality || gameMode == EditPosition) return;
5740   if(clear) {
5741     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5742   } else {
5743     int capt = 0;
5744     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5745     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5746       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5747       if(capt)
5748       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5749     }
5750   }
5751   DrawPosition(TRUE, NULL);
5752 }
5753
5754 void LeftClick(ClickType clickType, int xPix, int yPix)
5755 {
5756     int x, y;
5757     Boolean saveAnimate;
5758     static int second = 0, promotionChoice = 0;
5759     char promoChoice = NULLCHAR;
5760
5761     if (clickType == Press) ErrorPopDown();
5762     MarkTargetSquares(1);
5763
5764     x = EventToSquare(xPix, BOARD_WIDTH);
5765     y = EventToSquare(yPix, BOARD_HEIGHT);
5766     if (!flipView && y >= 0) {
5767         y = BOARD_HEIGHT - 1 - y;
5768     }
5769     if (flipView && x >= 0) {
5770         x = BOARD_WIDTH - 1 - x;
5771     }
5772
5773     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5774         if(clickType == Release) return; // ignore upclick of click-click destination
5775         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5776         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5777         if(gameInfo.holdingsWidth && 
5778                 (WhiteOnMove(currentMove) 
5779                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5780                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5781             // click in right holdings, for determining promotion piece
5782             ChessSquare p = boards[currentMove][y][x];
5783             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5784             if(p != EmptySquare) {
5785                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5786                 fromX = fromY = -1;
5787                 return;
5788             }
5789         }
5790         DrawPosition(FALSE, boards[currentMove]);
5791         return;
5792     }
5793
5794     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5795     if(clickType == Press
5796             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5797               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5798               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5799         return;
5800
5801     if (fromX == -1) {
5802         if (clickType == Press) {
5803             /* First square */
5804             if (OKToStartUserMove(x, y)) {
5805                 fromX = x;
5806                 fromY = y;
5807                 second = 0;
5808                 MarkTargetSquares(0);
5809                 DragPieceBegin(xPix, yPix);
5810                 if (appData.highlightDragging) {
5811                     SetHighlights(x, y, -1, -1);
5812                 }
5813             }
5814         }
5815         return;
5816     }
5817
5818     /* fromX != -1 */
5819     if (clickType == Press && gameMode != EditPosition) {
5820         ChessSquare fromP;
5821         ChessSquare toP;
5822         int frc;
5823
5824         // ignore off-board to clicks
5825         if(y < 0 || x < 0) return;
5826
5827         /* Check if clicking again on the same color piece */
5828         fromP = boards[currentMove][fromY][fromX];
5829         toP = boards[currentMove][y][x];
5830         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5831         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5832              WhitePawn <= toP && toP <= WhiteKing &&
5833              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5834              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5835             (BlackPawn <= fromP && fromP <= BlackKing && 
5836              BlackPawn <= toP && toP <= BlackKing &&
5837              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5838              !(fromP == BlackKing && toP == BlackRook && frc))) {
5839             /* Clicked again on same color piece -- changed his mind */
5840             second = (x == fromX && y == fromY);
5841             if (appData.highlightDragging) {
5842                 SetHighlights(x, y, -1, -1);
5843             } else {
5844                 ClearHighlights();
5845             }
5846             if (OKToStartUserMove(x, y)) {
5847                 fromX = x;
5848                 fromY = y;
5849                 MarkTargetSquares(0);
5850                 DragPieceBegin(xPix, yPix);
5851             }
5852             return;
5853         }
5854         // ignore clicks on holdings
5855         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5856     }
5857
5858     if (clickType == Release && x == fromX && y == fromY) {
5859         DragPieceEnd(xPix, yPix);
5860         if (appData.animateDragging) {
5861             /* Undo animation damage if any */
5862             DrawPosition(FALSE, NULL);
5863         }
5864         if (second) {
5865             /* Second up/down in same square; just abort move */
5866             second = 0;
5867             fromX = fromY = -1;
5868             ClearHighlights();
5869             gotPremove = 0;
5870             ClearPremoveHighlights();
5871         } else {
5872             /* First upclick in same square; start click-click mode */
5873             SetHighlights(x, y, -1, -1);
5874         }
5875         return;
5876     }
5877
5878     /* we now have a different from- and (possibly off-board) to-square */
5879     /* Completed move */
5880     toX = x;
5881     toY = y;
5882     saveAnimate = appData.animate;
5883     if (clickType == Press) {
5884         /* Finish clickclick move */
5885         if (appData.animate || appData.highlightLastMove) {
5886             SetHighlights(fromX, fromY, toX, toY);
5887         } else {
5888             ClearHighlights();
5889         }
5890     } else {
5891         /* Finish drag move */
5892         if (appData.highlightLastMove) {
5893             SetHighlights(fromX, fromY, toX, toY);
5894         } else {
5895             ClearHighlights();
5896         }
5897         DragPieceEnd(xPix, yPix);
5898         /* Don't animate move and drag both */
5899         appData.animate = FALSE;
5900     }
5901
5902     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5903     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5904         ChessSquare piece = boards[currentMove][fromY][fromX];
5905         if(gameMode == EditPosition && piece != EmptySquare &&
5906            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5907             int n;
5908              
5909             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5910                 n = PieceToNumber(piece - (int)BlackPawn);
5911                 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5912                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5913                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5914             } else
5915             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5916                 n = PieceToNumber(piece);
5917                 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5918                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5919                 boards[currentMove][n][BOARD_WIDTH-2]++;
5920             }
5921             boards[currentMove][fromY][fromX] = EmptySquare;
5922         }
5923         ClearHighlights();
5924         fromX = fromY = -1;
5925         DrawPosition(TRUE, boards[currentMove]);
5926         return;
5927     }
5928
5929     // off-board moves should not be highlighted
5930     if(x < 0 || x < 0) ClearHighlights();
5931
5932     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5933         SetHighlights(fromX, fromY, toX, toY);
5934         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5935             // [HGM] super: promotion to captured piece selected from holdings
5936             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5937             promotionChoice = TRUE;
5938             // kludge follows to temporarily execute move on display, without promoting yet
5939             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5940             boards[currentMove][toY][toX] = p;
5941             DrawPosition(FALSE, boards[currentMove]);
5942             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5943             boards[currentMove][toY][toX] = q;
5944             DisplayMessage("Click in holdings to choose piece", "");
5945             return;
5946         }
5947         PromotionPopUp();
5948     } else {
5949         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5950         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5951         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5952         fromX = fromY = -1;
5953     }
5954     appData.animate = saveAnimate;
5955     if (appData.animate || appData.animateDragging) {
5956         /* Undo animation damage if needed */
5957         DrawPosition(FALSE, NULL);
5958     }
5959 }
5960
5961 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5962 {
5963 //    char * hint = lastHint;
5964     FrontEndProgramStats stats;
5965
5966     stats.which = cps == &first ? 0 : 1;
5967     stats.depth = cpstats->depth;
5968     stats.nodes = cpstats->nodes;
5969     stats.score = cpstats->score;
5970     stats.time = cpstats->time;
5971     stats.pv = cpstats->movelist;
5972     stats.hint = lastHint;
5973     stats.an_move_index = 0;
5974     stats.an_move_count = 0;
5975
5976     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5977         stats.hint = cpstats->move_name;
5978         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5979         stats.an_move_count = cpstats->nr_moves;
5980     }
5981
5982     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5983
5984     SetProgramStats( &stats );
5985 }
5986
5987 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5988 {   // [HGM] book: this routine intercepts moves to simulate book replies
5989     char *bookHit = NULL;
5990
5991     //first determine if the incoming move brings opponent into his book
5992     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5993         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5994     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5995     if(bookHit != NULL && !cps->bookSuspend) {
5996         // make sure opponent is not going to reply after receiving move to book position
5997         SendToProgram("force\n", cps);
5998         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5999     }
6000     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6001     // now arrange restart after book miss
6002     if(bookHit) {
6003         // after a book hit we never send 'go', and the code after the call to this routine
6004         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6005         char buf[MSG_SIZ];
6006         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6007         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6008         SendToProgram(buf, cps);
6009         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6010     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6011         SendToProgram("go\n", cps);
6012         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6013     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6014         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6015             SendToProgram("go\n", cps);
6016         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6017     }
6018     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6019 }
6020
6021 char *savedMessage;
6022 ChessProgramState *savedState;
6023 void DeferredBookMove(void)
6024 {
6025         if(savedState->lastPing != savedState->lastPong)
6026                     ScheduleDelayedEvent(DeferredBookMove, 10);
6027         else
6028         HandleMachineMove(savedMessage, savedState);
6029 }
6030
6031 void
6032 HandleMachineMove(message, cps)
6033      char *message;
6034      ChessProgramState *cps;
6035 {
6036     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6037     char realname[MSG_SIZ];
6038     int fromX, fromY, toX, toY;
6039     ChessMove moveType;
6040     char promoChar;
6041     char *p;
6042     int machineWhite;
6043     char *bookHit;
6044
6045     cps->userError = 0;
6046
6047 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6048     /*
6049      * Kludge to ignore BEL characters
6050      */
6051     while (*message == '\007') message++;
6052
6053     /*
6054      * [HGM] engine debug message: ignore lines starting with '#' character
6055      */
6056     if(cps->debug && *message == '#') return;
6057
6058     /*
6059      * Look for book output
6060      */
6061     if (cps == &first && bookRequested) {
6062         if (message[0] == '\t' || message[0] == ' ') {
6063             /* Part of the book output is here; append it */
6064             strcat(bookOutput, message);
6065             strcat(bookOutput, "  \n");
6066             return;
6067         } else if (bookOutput[0] != NULLCHAR) {
6068             /* All of book output has arrived; display it */
6069             char *p = bookOutput;
6070             while (*p != NULLCHAR) {
6071                 if (*p == '\t') *p = ' ';
6072                 p++;
6073             }
6074             DisplayInformation(bookOutput);
6075             bookRequested = FALSE;
6076             /* Fall through to parse the current output */
6077         }
6078     }
6079
6080     /*
6081      * Look for machine move.
6082      */
6083     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6084         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6085     {
6086         /* This method is only useful on engines that support ping */
6087         if (cps->lastPing != cps->lastPong) {
6088           if (gameMode == BeginningOfGame) {
6089             /* Extra move from before last new; ignore */
6090             if (appData.debugMode) {
6091                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6092             }
6093           } else {
6094             if (appData.debugMode) {
6095                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6096                         cps->which, gameMode);
6097             }
6098
6099             SendToProgram("undo\n", cps);
6100           }
6101           return;
6102         }
6103
6104         switch (gameMode) {
6105           case BeginningOfGame:
6106             /* Extra move from before last reset; ignore */
6107             if (appData.debugMode) {
6108                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6109             }
6110             return;
6111
6112           case EndOfGame:
6113           case IcsIdle:
6114           default:
6115             /* Extra move after we tried to stop.  The mode test is
6116                not a reliable way of detecting this problem, but it's
6117                the best we can do on engines that don't support ping.
6118             */
6119             if (appData.debugMode) {
6120                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6121                         cps->which, gameMode);
6122             }
6123             SendToProgram("undo\n", cps);
6124             return;
6125
6126           case MachinePlaysWhite:
6127           case IcsPlayingWhite:
6128             machineWhite = TRUE;
6129             break;
6130
6131           case MachinePlaysBlack:
6132           case IcsPlayingBlack:
6133             machineWhite = FALSE;
6134             break;
6135
6136           case TwoMachinesPlay:
6137             machineWhite = (cps->twoMachinesColor[0] == 'w');
6138             break;
6139         }
6140         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6141             if (appData.debugMode) {
6142                 fprintf(debugFP,
6143                         "Ignoring move out of turn by %s, gameMode %d"
6144                         ", forwardMost %d\n",
6145                         cps->which, gameMode, forwardMostMove);
6146             }
6147             return;
6148         }
6149
6150     if (appData.debugMode) { int f = forwardMostMove;
6151         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6152                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6153                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6154     }
6155         if(cps->alphaRank) AlphaRank(machineMove, 4);
6156         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6157                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6158             /* Machine move could not be parsed; ignore it. */
6159             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6160                     machineMove, cps->which);
6161             DisplayError(buf1, 0);
6162             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6163                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6164             if (gameMode == TwoMachinesPlay) {
6165               GameEnds(machineWhite ? BlackWins : WhiteWins,
6166                        buf1, GE_XBOARD);
6167             }
6168             return;
6169         }
6170
6171         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6172         /* So we have to redo legality test with true e.p. status here,  */
6173         /* to make sure an illegal e.p. capture does not slip through,   */
6174         /* to cause a forfeit on a justified illegal-move complaint      */
6175         /* of the opponent.                                              */
6176         if( gameMode==TwoMachinesPlay && appData.testLegality
6177             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6178                                                               ) {
6179            ChessMove moveType;
6180            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6181                              fromY, fromX, toY, toX, promoChar);
6182             if (appData.debugMode) {
6183                 int i;
6184                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6185                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6186                 fprintf(debugFP, "castling rights\n");
6187             }
6188             if(moveType == IllegalMove) {
6189                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6190                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6191                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6192                            buf1, GE_XBOARD);
6193                 return;
6194            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6195            /* [HGM] Kludge to handle engines that send FRC-style castling
6196               when they shouldn't (like TSCP-Gothic) */
6197            switch(moveType) {
6198              case WhiteASideCastleFR:
6199              case BlackASideCastleFR:
6200                toX+=2;
6201                currentMoveString[2]++;
6202                break;
6203              case WhiteHSideCastleFR:
6204              case BlackHSideCastleFR:
6205                toX--;
6206                currentMoveString[2]--;
6207                break;
6208              default: ; // nothing to do, but suppresses warning of pedantic compilers
6209            }
6210         }
6211         hintRequested = FALSE;
6212         lastHint[0] = NULLCHAR;
6213         bookRequested = FALSE;
6214         /* Program may be pondering now */
6215         cps->maybeThinking = TRUE;
6216         if (cps->sendTime == 2) cps->sendTime = 1;
6217         if (cps->offeredDraw) cps->offeredDraw--;
6218
6219 #if ZIPPY
6220         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6221             first.initDone) {
6222           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6223           ics_user_moved = 1;
6224           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6225                 char buf[3*MSG_SIZ];
6226
6227                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6228                         programStats.score / 100.,
6229                         programStats.depth,
6230                         programStats.time / 100.,
6231                         (unsigned int)programStats.nodes,
6232                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6233                         programStats.movelist);
6234                 SendToICS(buf);
6235 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6236           }
6237         }
6238 #endif
6239         /* currentMoveString is set as a side-effect of ParseOneMove */
6240         strcpy(machineMove, currentMoveString);
6241         strcat(machineMove, "\n");
6242         strcpy(moveList[forwardMostMove], machineMove);
6243
6244         /* [AS] Save move info and clear stats for next move */
6245         pvInfoList[ forwardMostMove ].score = programStats.score;
6246         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6247         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6248         ClearProgramStats();
6249         thinkOutput[0] = NULLCHAR;
6250         hiddenThinkOutputState = 0;
6251
6252         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6253
6254         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6255         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6256             int count = 0;
6257
6258             while( count < adjudicateLossPlies ) {
6259                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6260
6261                 if( count & 1 ) {
6262                     score = -score; /* Flip score for winning side */
6263                 }
6264
6265                 if( score > adjudicateLossThreshold ) {
6266                     break;
6267                 }
6268
6269                 count++;
6270             }
6271
6272             if( count >= adjudicateLossPlies ) {
6273                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6274
6275                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6276                     "Xboard adjudication",
6277                     GE_XBOARD );
6278
6279                 return;
6280             }
6281         }
6282
6283         if( gameMode == TwoMachinesPlay ) {
6284           // [HGM] some adjudications useful with buggy engines
6285             int k, count = 0; static int bare = 1;
6286           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6287
6288
6289             if( appData.testLegality )
6290             {   /* [HGM] Some more adjudications for obstinate engines */
6291                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6292                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6293                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6294                 static int moveCount = 6;
6295                 ChessMove result;
6296                 char *reason = NULL;
6297
6298                 /* Count what is on board. */
6299                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6300                 {   ChessSquare p = boards[forwardMostMove][i][j];
6301                     int m=i;
6302
6303                     switch((int) p)
6304                     {   /* count B,N,R and other of each side */
6305                         case WhiteKing:
6306                         case BlackKing:
6307                              NrK++; break; // [HGM] atomic: count Kings
6308                         case WhiteKnight:
6309                              NrWN++; break;
6310                         case WhiteBishop:
6311                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6312                              bishopsColor |= 1 << ((i^j)&1);
6313                              NrWB++; break;
6314                         case BlackKnight:
6315                              NrBN++; break;
6316                         case BlackBishop:
6317                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6318                              bishopsColor |= 1 << ((i^j)&1);
6319                              NrBB++; break;
6320                         case WhiteRook:
6321                              NrWR++; break;
6322                         case BlackRook:
6323                              NrBR++; break;
6324                         case WhiteQueen:
6325                              NrWQ++; break;
6326                         case BlackQueen:
6327                              NrBQ++; break;
6328                         case EmptySquare:
6329                              break;
6330                         case BlackPawn:
6331                              m = 7-i;
6332                         case WhitePawn:
6333                              PawnAdvance += m; NrPawns++;
6334                     }
6335                     NrPieces += (p != EmptySquare);
6336                     NrW += ((int)p < (int)BlackPawn);
6337                     if(gameInfo.variant == VariantXiangqi &&
6338                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6339                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6340                         NrW -= ((int)p < (int)BlackPawn);
6341                     }
6342                 }
6343
6344                 /* Some material-based adjudications that have to be made before stalemate test */
6345                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6346                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6347                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6348                      if(appData.checkMates) {
6349                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6350                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6351                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6352                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6353                          return;
6354                      }
6355                 }
6356
6357                 /* Bare King in Shatranj (loses) or Losers (wins) */
6358                 if( NrW == 1 || NrPieces - NrW == 1) {
6359                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6360                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6361                      if(appData.checkMates) {
6362                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6363                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6364                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6365                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6366                          return;
6367                      }
6368                   } else
6369                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6370                   {    /* bare King */
6371                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6372                         if(appData.checkMates) {
6373                             /* but only adjudicate if adjudication enabled */
6374                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6375                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6376                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6377                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6378                             return;
6379                         }
6380                   }
6381                 } else bare = 1;
6382
6383
6384             // don't wait for engine to announce game end if we can judge ourselves
6385             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6386               case MT_CHECK:
6387                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6388                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6389                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6390                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6391                             checkCnt++;
6392                         if(checkCnt >= 2) {
6393                             reason = "Xboard adjudication: 3rd check";
6394                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6395                             break;
6396                         }
6397                     }
6398                 }
6399               case MT_NONE:
6400               default:
6401                 break;
6402               case MT_STALEMATE:
6403               case MT_STAINMATE:
6404                 reason = "Xboard adjudication: Stalemate";
6405                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6406                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6407                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6408                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6409                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6410                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6411                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6412                                                                         EP_CHECKMATE : EP_WINS);
6413                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6414                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6415                 }
6416                 break;
6417               case MT_CHECKMATE:
6418                 reason = "Xboard adjudication: Checkmate";
6419                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6420                 break;
6421             }
6422
6423                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6424                     case EP_STALEMATE:
6425                         result = GameIsDrawn; break;
6426                     case EP_CHECKMATE:
6427                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6428                     case EP_WINS:
6429                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6430                     default:
6431                         result = (ChessMove) 0;
6432                 }
6433                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6434                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6435                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6436                     GameEnds( result, reason, GE_XBOARD );
6437                     return;
6438                 }
6439
6440                 /* Next absolutely insufficient mating material. */
6441                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6442                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6443                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6444                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6445                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6446
6447                      /* always flag draws, for judging claims */
6448                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6449
6450                      if(appData.materialDraws) {
6451                          /* but only adjudicate them if adjudication enabled */
6452                          SendToProgram("force\n", cps->other); // suppress reply
6453                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6454                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6455                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6456                          return;
6457                      }
6458                 }
6459
6460                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6461                 if(NrPieces == 4 &&
6462                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6463                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6464                    || NrWN==2 || NrBN==2     /* KNNK */
6465                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6466                   ) ) {
6467                      if(--moveCount < 0 && appData.trivialDraws)
6468                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6469                           SendToProgram("force\n", cps->other); // suppress reply
6470                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6471                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6472                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6473                           return;
6474                      }
6475                 } else moveCount = 6;
6476             }
6477           }
6478           
6479           if (appData.debugMode) { int i;
6480             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6481                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6482                     appData.drawRepeats);
6483             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6484               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6485             
6486           }
6487
6488                 /* Check for rep-draws */
6489                 count = 0;
6490                 for(k = forwardMostMove-2;
6491                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6492                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6493                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6494                     k-=2)
6495                 {   int rights=0;
6496                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6497                         /* compare castling rights */
6498                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6499                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6500                                 rights++; /* King lost rights, while rook still had them */
6501                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6502                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6503                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6504                                    rights++; /* but at least one rook lost them */
6505                         }
6506                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6507                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6508                                 rights++; 
6509                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6510                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6511                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6512                                    rights++;
6513                         }
6514                         if( rights == 0 && ++count > appData.drawRepeats-2
6515                             && appData.drawRepeats > 1) {
6516                              /* adjudicate after user-specified nr of repeats */
6517                              SendToProgram("force\n", cps->other); // suppress reply
6518                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6519                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6520                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6521                                 // [HGM] xiangqi: check for forbidden perpetuals
6522                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6523                                 for(m=forwardMostMove; m>k; m-=2) {
6524                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6525                                         ourPerpetual = 0; // the current mover did not always check
6526                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6527                                         hisPerpetual = 0; // the opponent did not always check
6528                                 }
6529                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6530                                                                         ourPerpetual, hisPerpetual);
6531                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6532                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6533                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6534                                     return;
6535                                 }
6536                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6537                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6538                                 // Now check for perpetual chases
6539                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6540                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6541                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6542                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6543                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6544                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6545                                         return;
6546                                     }
6547                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6548                                         break; // Abort repetition-checking loop.
6549                                 }
6550                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6551                              }
6552                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6553                              return;
6554                         }
6555                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6556                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6557                     }
6558                 }
6559
6560                 /* Now we test for 50-move draws. Determine ply count */
6561                 count = forwardMostMove;
6562                 /* look for last irreversble move */
6563                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6564                     count--;
6565                 /* if we hit starting position, add initial plies */
6566                 if( count == backwardMostMove )
6567                     count -= initialRulePlies;
6568                 count = forwardMostMove - count;
6569                 if( count >= 100)
6570                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6571                          /* this is used to judge if draw claims are legal */
6572                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6573                          SendToProgram("force\n", cps->other); // suppress reply
6574                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6575                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6576                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6577                          return;
6578                 }
6579
6580                 /* if draw offer is pending, treat it as a draw claim
6581                  * when draw condition present, to allow engines a way to
6582                  * claim draws before making their move to avoid a race
6583                  * condition occurring after their move
6584                  */
6585                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6586                          char *p = NULL;
6587                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6588                              p = "Draw claim: 50-move rule";
6589                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6590                              p = "Draw claim: 3-fold repetition";
6591                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6592                              p = "Draw claim: insufficient mating material";
6593                          if( p != NULL ) {
6594                              SendToProgram("force\n", cps->other); // suppress reply
6595                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6596                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6597                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6598                              return;
6599                          }
6600                 }
6601
6602
6603                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6604                     SendToProgram("force\n", cps->other); // suppress reply
6605                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6606                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6607
6608                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6609
6610                     return;
6611                 }
6612         }
6613
6614         bookHit = NULL;
6615         if (gameMode == TwoMachinesPlay) {
6616             /* [HGM] relaying draw offers moved to after reception of move */
6617             /* and interpreting offer as claim if it brings draw condition */
6618             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6619                 SendToProgram("draw\n", cps->other);
6620             }
6621             if (cps->other->sendTime) {
6622                 SendTimeRemaining(cps->other,
6623                                   cps->other->twoMachinesColor[0] == 'w');
6624             }
6625             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6626             if (firstMove && !bookHit) {
6627                 firstMove = FALSE;
6628                 if (cps->other->useColors) {
6629                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6630                 }
6631                 SendToProgram("go\n", cps->other);
6632             }
6633             cps->other->maybeThinking = TRUE;
6634         }
6635
6636         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6637
6638         if (!pausing && appData.ringBellAfterMoves) {
6639             RingBell();
6640         }
6641
6642         /*
6643          * Reenable menu items that were disabled while
6644          * machine was thinking
6645          */
6646         if (gameMode != TwoMachinesPlay)
6647             SetUserThinkingEnables();
6648
6649         // [HGM] book: after book hit opponent has received move and is now in force mode
6650         // force the book reply into it, and then fake that it outputted this move by jumping
6651         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6652         if(bookHit) {
6653                 static char bookMove[MSG_SIZ]; // a bit generous?
6654
6655                 strcpy(bookMove, "move ");
6656                 strcat(bookMove, bookHit);
6657                 message = bookMove;
6658                 cps = cps->other;
6659                 programStats.nodes = programStats.depth = programStats.time =
6660                 programStats.score = programStats.got_only_move = 0;
6661                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6662
6663                 if(cps->lastPing != cps->lastPong) {
6664                     savedMessage = message; // args for deferred call
6665                     savedState = cps;
6666                     ScheduleDelayedEvent(DeferredBookMove, 10);
6667                     return;
6668                 }
6669                 goto FakeBookMove;
6670         }
6671
6672         return;
6673     }
6674
6675     /* Set special modes for chess engines.  Later something general
6676      *  could be added here; for now there is just one kludge feature,
6677      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6678      *  when "xboard" is given as an interactive command.
6679      */
6680     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6681         cps->useSigint = FALSE;
6682         cps->useSigterm = FALSE;
6683     }
6684     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6685       ParseFeatures(message+8, cps);
6686       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6687     }
6688
6689     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6690      * want this, I was asked to put it in, and obliged.
6691      */
6692     if (!strncmp(message, "setboard ", 9)) {
6693         Board initial_position;
6694
6695         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6696
6697         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6698             DisplayError(_("Bad FEN received from engine"), 0);
6699             return ;
6700         } else {
6701            Reset(TRUE, FALSE);
6702            CopyBoard(boards[0], initial_position);
6703            initialRulePlies = FENrulePlies;
6704            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6705            else gameMode = MachinePlaysBlack;
6706            DrawPosition(FALSE, boards[currentMove]);
6707         }
6708         return;
6709     }
6710
6711     /*
6712      * Look for communication commands
6713      */
6714     if (!strncmp(message, "telluser ", 9)) {
6715         DisplayNote(message + 9);
6716         return;
6717     }
6718     if (!strncmp(message, "tellusererror ", 14)) {
6719         cps->userError = 1;
6720         DisplayError(message + 14, 0);
6721         return;
6722     }
6723     if (!strncmp(message, "tellopponent ", 13)) {
6724       if (appData.icsActive) {
6725         if (loggedOn) {
6726           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6727           SendToICS(buf1);
6728         }
6729       } else {
6730         DisplayNote(message + 13);
6731       }
6732       return;
6733     }
6734     if (!strncmp(message, "tellothers ", 11)) {
6735       if (appData.icsActive) {
6736         if (loggedOn) {
6737           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6738           SendToICS(buf1);
6739         }
6740       }
6741       return;
6742     }
6743     if (!strncmp(message, "tellall ", 8)) {
6744       if (appData.icsActive) {
6745         if (loggedOn) {
6746           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6747           SendToICS(buf1);
6748         }
6749       } else {
6750         DisplayNote(message + 8);
6751       }
6752       return;
6753     }
6754     if (strncmp(message, "warning", 7) == 0) {
6755         /* Undocumented feature, use tellusererror in new code */
6756         DisplayError(message, 0);
6757         return;
6758     }
6759     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6760         strcpy(realname, cps->tidy);
6761         strcat(realname, " query");
6762         AskQuestion(realname, buf2, buf1, cps->pr);
6763         return;
6764     }
6765     /* Commands from the engine directly to ICS.  We don't allow these to be
6766      *  sent until we are logged on. Crafty kibitzes have been known to
6767      *  interfere with the login process.
6768      */
6769     if (loggedOn) {
6770         if (!strncmp(message, "tellics ", 8)) {
6771             SendToICS(message + 8);
6772             SendToICS("\n");
6773             return;
6774         }
6775         if (!strncmp(message, "tellicsnoalias ", 15)) {
6776             SendToICS(ics_prefix);
6777             SendToICS(message + 15);
6778             SendToICS("\n");
6779             return;
6780         }
6781         /* The following are for backward compatibility only */
6782         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6783             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6784             SendToICS(ics_prefix);
6785             SendToICS(message);
6786             SendToICS("\n");
6787             return;
6788         }
6789     }
6790     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6791         return;
6792     }
6793     /*
6794      * If the move is illegal, cancel it and redraw the board.
6795      * Also deal with other error cases.  Matching is rather loose
6796      * here to accommodate engines written before the spec.
6797      */
6798     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6799         strncmp(message, "Error", 5) == 0) {
6800         if (StrStr(message, "name") ||
6801             StrStr(message, "rating") || StrStr(message, "?") ||
6802             StrStr(message, "result") || StrStr(message, "board") ||
6803             StrStr(message, "bk") || StrStr(message, "computer") ||
6804             StrStr(message, "variant") || StrStr(message, "hint") ||
6805             StrStr(message, "random") || StrStr(message, "depth") ||
6806             StrStr(message, "accepted")) {
6807             return;
6808         }
6809         if (StrStr(message, "protover")) {
6810           /* Program is responding to input, so it's apparently done
6811              initializing, and this error message indicates it is
6812              protocol version 1.  So we don't need to wait any longer
6813              for it to initialize and send feature commands. */
6814           FeatureDone(cps, 1);
6815           cps->protocolVersion = 1;
6816           return;
6817         }
6818         cps->maybeThinking = FALSE;
6819
6820         if (StrStr(message, "draw")) {
6821             /* Program doesn't have "draw" command */
6822             cps->sendDrawOffers = 0;
6823             return;
6824         }
6825         if (cps->sendTime != 1 &&
6826             (StrStr(message, "time") || StrStr(message, "otim"))) {
6827           /* Program apparently doesn't have "time" or "otim" command */
6828           cps->sendTime = 0;
6829           return;
6830         }
6831         if (StrStr(message, "analyze")) {
6832             cps->analysisSupport = FALSE;
6833             cps->analyzing = FALSE;
6834             Reset(FALSE, TRUE);
6835             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6836             DisplayError(buf2, 0);
6837             return;
6838         }
6839         if (StrStr(message, "(no matching move)st")) {
6840           /* Special kludge for GNU Chess 4 only */
6841           cps->stKludge = TRUE;
6842           SendTimeControl(cps, movesPerSession, timeControl,
6843                           timeIncrement, appData.searchDepth,
6844                           searchTime);
6845           return;
6846         }
6847         if (StrStr(message, "(no matching move)sd")) {
6848           /* Special kludge for GNU Chess 4 only */
6849           cps->sdKludge = TRUE;
6850           SendTimeControl(cps, movesPerSession, timeControl,
6851                           timeIncrement, appData.searchDepth,
6852                           searchTime);
6853           return;
6854         }
6855         if (!StrStr(message, "llegal")) {
6856             return;
6857         }
6858         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6859             gameMode == IcsIdle) return;
6860         if (forwardMostMove <= backwardMostMove) return;
6861         if (pausing) PauseEvent();
6862       if(appData.forceIllegal) {
6863             // [HGM] illegal: machine refused move; force position after move into it
6864           SendToProgram("force\n", cps);
6865           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6866                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6867                 // when black is to move, while there might be nothing on a2 or black
6868                 // might already have the move. So send the board as if white has the move.
6869                 // But first we must change the stm of the engine, as it refused the last move
6870                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6871                 if(WhiteOnMove(forwardMostMove)) {
6872                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6873                     SendBoard(cps, forwardMostMove); // kludgeless board
6874                 } else {
6875                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6876                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6877                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6878                 }
6879           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6880             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6881                  gameMode == TwoMachinesPlay)
6882               SendToProgram("go\n", cps);
6883             return;
6884       } else
6885         if (gameMode == PlayFromGameFile) {
6886             /* Stop reading this game file */
6887             gameMode = EditGame;
6888             ModeHighlight();
6889         }
6890         currentMove = --forwardMostMove;
6891         DisplayMove(currentMove-1); /* before DisplayMoveError */
6892         SwitchClocks();
6893         DisplayBothClocks();
6894         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6895                 parseList[currentMove], cps->which);
6896         DisplayMoveError(buf1);
6897         DrawPosition(FALSE, boards[currentMove]);
6898
6899         /* [HGM] illegal-move claim should forfeit game when Xboard */
6900         /* only passes fully legal moves                            */
6901         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6902             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6903                                 "False illegal-move claim", GE_XBOARD );
6904         }
6905         return;
6906     }
6907     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6908         /* Program has a broken "time" command that
6909            outputs a string not ending in newline.
6910            Don't use it. */
6911         cps->sendTime = 0;
6912     }
6913
6914     /*
6915      * If chess program startup fails, exit with an error message.
6916      * Attempts to recover here are futile.
6917      */
6918     if ((StrStr(message, "unknown host") != NULL)
6919         || (StrStr(message, "No remote directory") != NULL)
6920         || (StrStr(message, "not found") != NULL)
6921         || (StrStr(message, "No such file") != NULL)
6922         || (StrStr(message, "can't alloc") != NULL)
6923         || (StrStr(message, "Permission denied") != NULL)) {
6924
6925         cps->maybeThinking = FALSE;
6926         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6927                 cps->which, cps->program, cps->host, message);
6928         RemoveInputSource(cps->isr);
6929         DisplayFatalError(buf1, 0, 1);
6930         return;
6931     }
6932
6933     /*
6934      * Look for hint output
6935      */
6936     if (sscanf(message, "Hint: %s", buf1) == 1) {
6937         if (cps == &first && hintRequested) {
6938             hintRequested = FALSE;
6939             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6940                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6941                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6942                                     PosFlags(forwardMostMove),
6943                                     fromY, fromX, toY, toX, promoChar, buf1);
6944                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6945                 DisplayInformation(buf2);
6946             } else {
6947                 /* Hint move could not be parsed!? */
6948               snprintf(buf2, sizeof(buf2),
6949                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6950                         buf1, cps->which);
6951                 DisplayError(buf2, 0);
6952             }
6953         } else {
6954             strcpy(lastHint, buf1);
6955         }
6956         return;
6957     }
6958
6959     /*
6960      * Ignore other messages if game is not in progress
6961      */
6962     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6963         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6964
6965     /*
6966      * look for win, lose, draw, or draw offer
6967      */
6968     if (strncmp(message, "1-0", 3) == 0) {
6969         char *p, *q, *r = "";
6970         p = strchr(message, '{');
6971         if (p) {
6972             q = strchr(p, '}');
6973             if (q) {
6974                 *q = NULLCHAR;
6975                 r = p + 1;
6976             }
6977         }
6978         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6979         return;
6980     } else if (strncmp(message, "0-1", 3) == 0) {
6981         char *p, *q, *r = "";
6982         p = strchr(message, '{');
6983         if (p) {
6984             q = strchr(p, '}');
6985             if (q) {
6986                 *q = NULLCHAR;
6987                 r = p + 1;
6988             }
6989         }
6990         /* Kludge for Arasan 4.1 bug */
6991         if (strcmp(r, "Black resigns") == 0) {
6992             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6993             return;
6994         }
6995         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6996         return;
6997     } else if (strncmp(message, "1/2", 3) == 0) {
6998         char *p, *q, *r = "";
6999         p = strchr(message, '{');
7000         if (p) {
7001             q = strchr(p, '}');
7002             if (q) {
7003                 *q = NULLCHAR;
7004                 r = p + 1;
7005             }
7006         }
7007
7008         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7009         return;
7010
7011     } else if (strncmp(message, "White resign", 12) == 0) {
7012         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7013         return;
7014     } else if (strncmp(message, "Black resign", 12) == 0) {
7015         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7016         return;
7017     } else if (strncmp(message, "White matches", 13) == 0 ||
7018                strncmp(message, "Black matches", 13) == 0   ) {
7019         /* [HGM] ignore GNUShogi noises */
7020         return;
7021     } else if (strncmp(message, "White", 5) == 0 &&
7022                message[5] != '(' &&
7023                StrStr(message, "Black") == NULL) {
7024         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7025         return;
7026     } else if (strncmp(message, "Black", 5) == 0 &&
7027                message[5] != '(') {
7028         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7029         return;
7030     } else if (strcmp(message, "resign") == 0 ||
7031                strcmp(message, "computer resigns") == 0) {
7032         switch (gameMode) {
7033           case MachinePlaysBlack:
7034           case IcsPlayingBlack:
7035             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7036             break;
7037           case MachinePlaysWhite:
7038           case IcsPlayingWhite:
7039             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7040             break;
7041           case TwoMachinesPlay:
7042             if (cps->twoMachinesColor[0] == 'w')
7043               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7044             else
7045               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7046             break;
7047           default:
7048             /* can't happen */
7049             break;
7050         }
7051         return;
7052     } else if (strncmp(message, "opponent mates", 14) == 0) {
7053         switch (gameMode) {
7054           case MachinePlaysBlack:
7055           case IcsPlayingBlack:
7056             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7057             break;
7058           case MachinePlaysWhite:
7059           case IcsPlayingWhite:
7060             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7061             break;
7062           case TwoMachinesPlay:
7063             if (cps->twoMachinesColor[0] == 'w')
7064               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7065             else
7066               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7067             break;
7068           default:
7069             /* can't happen */
7070             break;
7071         }
7072         return;
7073     } else if (strncmp(message, "computer mates", 14) == 0) {
7074         switch (gameMode) {
7075           case MachinePlaysBlack:
7076           case IcsPlayingBlack:
7077             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7078             break;
7079           case MachinePlaysWhite:
7080           case IcsPlayingWhite:
7081             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7082             break;
7083           case TwoMachinesPlay:
7084             if (cps->twoMachinesColor[0] == 'w')
7085               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7086             else
7087               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7088             break;
7089           default:
7090             /* can't happen */
7091             break;
7092         }
7093         return;
7094     } else if (strncmp(message, "checkmate", 9) == 0) {
7095         if (WhiteOnMove(forwardMostMove)) {
7096             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7097         } else {
7098             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7099         }
7100         return;
7101     } else if (strstr(message, "Draw") != NULL ||
7102                strstr(message, "game is a draw") != NULL) {
7103         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7104         return;
7105     } else if (strstr(message, "offer") != NULL &&
7106                strstr(message, "draw") != NULL) {
7107 #if ZIPPY
7108         if (appData.zippyPlay && first.initDone) {
7109             /* Relay offer to ICS */
7110             SendToICS(ics_prefix);
7111             SendToICS("draw\n");
7112         }
7113 #endif
7114         cps->offeredDraw = 2; /* valid until this engine moves twice */
7115         if (gameMode == TwoMachinesPlay) {
7116             if (cps->other->offeredDraw) {
7117                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7118             /* [HGM] in two-machine mode we delay relaying draw offer      */
7119             /* until after we also have move, to see if it is really claim */
7120             }
7121         } else if (gameMode == MachinePlaysWhite ||
7122                    gameMode == MachinePlaysBlack) {
7123           if (userOfferedDraw) {
7124             DisplayInformation(_("Machine accepts your draw offer"));
7125             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7126           } else {
7127             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7128           }
7129         }
7130     }
7131
7132
7133     /*
7134      * Look for thinking output
7135      */
7136     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7137           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7138                                 ) {
7139         int plylev, mvleft, mvtot, curscore, time;
7140         char mvname[MOVE_LEN];
7141         u64 nodes; // [DM]
7142         char plyext;
7143         int ignore = FALSE;
7144         int prefixHint = FALSE;
7145         mvname[0] = NULLCHAR;
7146
7147         switch (gameMode) {
7148           case MachinePlaysBlack:
7149           case IcsPlayingBlack:
7150             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7151             break;
7152           case MachinePlaysWhite:
7153           case IcsPlayingWhite:
7154             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7155             break;
7156           case AnalyzeMode:
7157           case AnalyzeFile:
7158             break;
7159           case IcsObserving: /* [DM] icsEngineAnalyze */
7160             if (!appData.icsEngineAnalyze) ignore = TRUE;
7161             break;
7162           case TwoMachinesPlay:
7163             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7164                 ignore = TRUE;
7165             }
7166             break;
7167           default:
7168             ignore = TRUE;
7169             break;
7170         }
7171
7172         if (!ignore) {
7173             buf1[0] = NULLCHAR;
7174             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7175                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7176
7177                 if (plyext != ' ' && plyext != '\t') {
7178                     time *= 100;
7179                 }
7180
7181                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7182                 if( cps->scoreIsAbsolute && 
7183                     ( gameMode == MachinePlaysBlack ||
7184                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7185                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7186                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7187                      !WhiteOnMove(currentMove)
7188                     ) )
7189                 {
7190                     curscore = -curscore;
7191                 }
7192
7193
7194                 programStats.depth = plylev;
7195                 programStats.nodes = nodes;
7196                 programStats.time = time;
7197                 programStats.score = curscore;
7198                 programStats.got_only_move = 0;
7199
7200                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7201                         int ticklen;
7202
7203                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7204                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7205                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7206                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7207                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7208                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7209                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7210                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7211                 }
7212
7213                 /* Buffer overflow protection */
7214                 if (buf1[0] != NULLCHAR) {
7215                     if (strlen(buf1) >= sizeof(programStats.movelist)
7216                         && appData.debugMode) {
7217                         fprintf(debugFP,
7218                                 "PV is too long; using the first %u bytes.\n",
7219                                 (unsigned) sizeof(programStats.movelist) - 1);
7220                     }
7221
7222                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7223                 } else {
7224                     sprintf(programStats.movelist, " no PV\n");
7225                 }
7226
7227                 if (programStats.seen_stat) {
7228                     programStats.ok_to_send = 1;
7229                 }
7230
7231                 if (strchr(programStats.movelist, '(') != NULL) {
7232                     programStats.line_is_book = 1;
7233                     programStats.nr_moves = 0;
7234                     programStats.moves_left = 0;
7235                 } else {
7236                     programStats.line_is_book = 0;
7237                 }
7238
7239                 SendProgramStatsToFrontend( cps, &programStats );
7240
7241                 /*
7242                     [AS] Protect the thinkOutput buffer from overflow... this
7243                     is only useful if buf1 hasn't overflowed first!
7244                 */
7245                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7246                         plylev,
7247                         (gameMode == TwoMachinesPlay ?
7248                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7249                         ((double) curscore) / 100.0,
7250                         prefixHint ? lastHint : "",
7251                         prefixHint ? " " : "" );
7252
7253                 if( buf1[0] != NULLCHAR ) {
7254                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7255
7256                     if( strlen(buf1) > max_len ) {
7257                         if( appData.debugMode) {
7258                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7259                         }
7260                         buf1[max_len+1] = '\0';
7261                     }
7262
7263                     strcat( thinkOutput, buf1 );
7264                 }
7265
7266                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7267                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7268                     DisplayMove(currentMove - 1);
7269                 }
7270                 return;
7271
7272             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7273                 /* crafty (9.25+) says "(only move) <move>"
7274                  * if there is only 1 legal move
7275                  */
7276                 sscanf(p, "(only move) %s", buf1);
7277                 sprintf(thinkOutput, "%s (only move)", buf1);
7278                 sprintf(programStats.movelist, "%s (only move)", buf1);
7279                 programStats.depth = 1;
7280                 programStats.nr_moves = 1;
7281                 programStats.moves_left = 1;
7282                 programStats.nodes = 1;
7283                 programStats.time = 1;
7284                 programStats.got_only_move = 1;
7285
7286                 /* Not really, but we also use this member to
7287                    mean "line isn't going to change" (Crafty
7288                    isn't searching, so stats won't change) */
7289                 programStats.line_is_book = 1;
7290
7291                 SendProgramStatsToFrontend( cps, &programStats );
7292
7293                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7294                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7295                     DisplayMove(currentMove - 1);
7296                 }
7297                 return;
7298             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7299                               &time, &nodes, &plylev, &mvleft,
7300                               &mvtot, mvname) >= 5) {
7301                 /* The stat01: line is from Crafty (9.29+) in response
7302                    to the "." command */
7303                 programStats.seen_stat = 1;
7304                 cps->maybeThinking = TRUE;
7305
7306                 if (programStats.got_only_move || !appData.periodicUpdates)
7307                   return;
7308
7309                 programStats.depth = plylev;
7310                 programStats.time = time;
7311                 programStats.nodes = nodes;
7312                 programStats.moves_left = mvleft;
7313                 programStats.nr_moves = mvtot;
7314                 strcpy(programStats.move_name, mvname);
7315                 programStats.ok_to_send = 1;
7316                 programStats.movelist[0] = '\0';
7317
7318                 SendProgramStatsToFrontend( cps, &programStats );
7319
7320                 return;
7321
7322             } else if (strncmp(message,"++",2) == 0) {
7323                 /* Crafty 9.29+ outputs this */
7324                 programStats.got_fail = 2;
7325                 return;
7326
7327             } else if (strncmp(message,"--",2) == 0) {
7328                 /* Crafty 9.29+ outputs this */
7329                 programStats.got_fail = 1;
7330                 return;
7331
7332             } else if (thinkOutput[0] != NULLCHAR &&
7333                        strncmp(message, "    ", 4) == 0) {
7334                 unsigned message_len;
7335
7336                 p = message;
7337                 while (*p && *p == ' ') p++;
7338
7339                 message_len = strlen( p );
7340
7341                 /* [AS] Avoid buffer overflow */
7342                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7343                     strcat(thinkOutput, " ");
7344                     strcat(thinkOutput, p);
7345                 }
7346
7347                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7348                     strcat(programStats.movelist, " ");
7349                     strcat(programStats.movelist, p);
7350                 }
7351
7352                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7353                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7354                     DisplayMove(currentMove - 1);
7355                 }
7356                 return;
7357             }
7358         }
7359         else {
7360             buf1[0] = NULLCHAR;
7361
7362             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7363                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7364             {
7365                 ChessProgramStats cpstats;
7366
7367                 if (plyext != ' ' && plyext != '\t') {
7368                     time *= 100;
7369                 }
7370
7371                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7372                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7373                     curscore = -curscore;
7374                 }
7375
7376                 cpstats.depth = plylev;
7377                 cpstats.nodes = nodes;
7378                 cpstats.time = time;
7379                 cpstats.score = curscore;
7380                 cpstats.got_only_move = 0;
7381                 cpstats.movelist[0] = '\0';
7382
7383                 if (buf1[0] != NULLCHAR) {
7384                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7385                 }
7386
7387                 cpstats.ok_to_send = 0;
7388                 cpstats.line_is_book = 0;
7389                 cpstats.nr_moves = 0;
7390                 cpstats.moves_left = 0;
7391
7392                 SendProgramStatsToFrontend( cps, &cpstats );
7393             }
7394         }
7395     }
7396 }
7397
7398
7399 /* Parse a game score from the character string "game", and
7400    record it as the history of the current game.  The game
7401    score is NOT assumed to start from the standard position.
7402    The display is not updated in any way.
7403    */
7404 void
7405 ParseGameHistory(game)
7406      char *game;
7407 {
7408     ChessMove moveType;
7409     int fromX, fromY, toX, toY, boardIndex;
7410     char promoChar;
7411     char *p, *q;
7412     char buf[MSG_SIZ];
7413
7414     if (appData.debugMode)
7415       fprintf(debugFP, "Parsing game history: %s\n", game);
7416
7417     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7418     gameInfo.site = StrSave(appData.icsHost);
7419     gameInfo.date = PGNDate();
7420     gameInfo.round = StrSave("-");
7421
7422     /* Parse out names of players */
7423     while (*game == ' ') game++;
7424     p = buf;
7425     while (*game != ' ') *p++ = *game++;
7426     *p = NULLCHAR;
7427     gameInfo.white = StrSave(buf);
7428     while (*game == ' ') game++;
7429     p = buf;
7430     while (*game != ' ' && *game != '\n') *p++ = *game++;
7431     *p = NULLCHAR;
7432     gameInfo.black = StrSave(buf);
7433
7434     /* Parse moves */
7435     boardIndex = blackPlaysFirst ? 1 : 0;
7436     yynewstr(game);
7437     for (;;) {
7438         yyboardindex = boardIndex;
7439         moveType = (ChessMove) yylex();
7440         switch (moveType) {
7441           case IllegalMove:             /* maybe suicide chess, etc. */
7442   if (appData.debugMode) {
7443     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7444     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7445     setbuf(debugFP, NULL);
7446   }
7447           case WhitePromotionChancellor:
7448           case BlackPromotionChancellor:
7449           case WhitePromotionArchbishop:
7450           case BlackPromotionArchbishop:
7451           case WhitePromotionQueen:
7452           case BlackPromotionQueen:
7453           case WhitePromotionRook:
7454           case BlackPromotionRook:
7455           case WhitePromotionBishop:
7456           case BlackPromotionBishop:
7457           case WhitePromotionKnight:
7458           case BlackPromotionKnight:
7459           case WhitePromotionKing:
7460           case BlackPromotionKing:
7461           case NormalMove:
7462           case WhiteCapturesEnPassant:
7463           case BlackCapturesEnPassant:
7464           case WhiteKingSideCastle:
7465           case WhiteQueenSideCastle:
7466           case BlackKingSideCastle:
7467           case BlackQueenSideCastle:
7468           case WhiteKingSideCastleWild:
7469           case WhiteQueenSideCastleWild:
7470           case BlackKingSideCastleWild:
7471           case BlackQueenSideCastleWild:
7472           /* PUSH Fabien */
7473           case WhiteHSideCastleFR:
7474           case WhiteASideCastleFR:
7475           case BlackHSideCastleFR:
7476           case BlackASideCastleFR:
7477           /* POP Fabien */
7478             fromX = currentMoveString[0] - AAA;
7479             fromY = currentMoveString[1] - ONE;
7480             toX = currentMoveString[2] - AAA;
7481             toY = currentMoveString[3] - ONE;
7482             promoChar = currentMoveString[4];
7483             break;
7484           case WhiteDrop:
7485           case BlackDrop:
7486             fromX = moveType == WhiteDrop ?
7487               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7488             (int) CharToPiece(ToLower(currentMoveString[0]));
7489             fromY = DROP_RANK;
7490             toX = currentMoveString[2] - AAA;
7491             toY = currentMoveString[3] - ONE;
7492             promoChar = NULLCHAR;
7493             break;
7494           case AmbiguousMove:
7495             /* bug? */
7496             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7497   if (appData.debugMode) {
7498     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7499     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7500     setbuf(debugFP, NULL);
7501   }
7502             DisplayError(buf, 0);
7503             return;
7504           case ImpossibleMove:
7505             /* bug? */
7506             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7507   if (appData.debugMode) {
7508     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7509     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7510     setbuf(debugFP, NULL);
7511   }
7512             DisplayError(buf, 0);
7513             return;
7514           case (ChessMove) 0:   /* end of file */
7515             if (boardIndex < backwardMostMove) {
7516                 /* Oops, gap.  How did that happen? */
7517                 DisplayError(_("Gap in move list"), 0);
7518                 return;
7519             }
7520             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7521             if (boardIndex > forwardMostMove) {
7522                 forwardMostMove = boardIndex;
7523             }
7524             return;
7525           case ElapsedTime:
7526             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7527                 strcat(parseList[boardIndex-1], " ");
7528                 strcat(parseList[boardIndex-1], yy_text);
7529             }
7530             continue;
7531           case Comment:
7532           case PGNTag:
7533           case NAG:
7534           default:
7535             /* ignore */
7536             continue;
7537           case WhiteWins:
7538           case BlackWins:
7539           case GameIsDrawn:
7540           case GameUnfinished:
7541             if (gameMode == IcsExamining) {
7542                 if (boardIndex < backwardMostMove) {
7543                     /* Oops, gap.  How did that happen? */
7544                     return;
7545                 }
7546                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7547                 return;
7548             }
7549             gameInfo.result = moveType;
7550             p = strchr(yy_text, '{');
7551             if (p == NULL) p = strchr(yy_text, '(');
7552             if (p == NULL) {
7553                 p = yy_text;
7554                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7555             } else {
7556                 q = strchr(p, *p == '{' ? '}' : ')');
7557                 if (q != NULL) *q = NULLCHAR;
7558                 p++;
7559             }
7560             gameInfo.resultDetails = StrSave(p);
7561             continue;
7562         }
7563         if (boardIndex >= forwardMostMove &&
7564             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7565             backwardMostMove = blackPlaysFirst ? 1 : 0;
7566             return;
7567         }
7568         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7569                                  fromY, fromX, toY, toX, promoChar,
7570                                  parseList[boardIndex]);
7571         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7572         /* currentMoveString is set as a side-effect of yylex */
7573         strcpy(moveList[boardIndex], currentMoveString);
7574         strcat(moveList[boardIndex], "\n");
7575         boardIndex++;
7576         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7577         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7578           case MT_NONE:
7579           case MT_STALEMATE:
7580           default:
7581             break;
7582           case MT_CHECK:
7583             if(gameInfo.variant != VariantShogi)
7584                 strcat(parseList[boardIndex - 1], "+");
7585             break;
7586           case MT_CHECKMATE:
7587           case MT_STAINMATE:
7588             strcat(parseList[boardIndex - 1], "#");
7589             break;
7590         }
7591     }
7592 }
7593
7594
7595 /* Apply a move to the given board  */
7596 void
7597 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7598      int fromX, fromY, toX, toY;
7599      int promoChar;
7600      Board board;
7601 {
7602   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7603   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7604
7605     /* [HGM] compute & store e.p. status and castling rights for new position */
7606     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7607     { int i;
7608
7609       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7610       oldEP = (signed char)board[EP_STATUS];
7611       board[EP_STATUS] = EP_NONE;
7612
7613       if( board[toY][toX] != EmptySquare ) 
7614            board[EP_STATUS] = EP_CAPTURE;  
7615
7616       if( board[fromY][fromX] == WhitePawn ) {
7617            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7618                board[EP_STATUS] = EP_PAWN_MOVE;
7619            if( toY-fromY==2) {
7620                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7621                         gameInfo.variant != VariantBerolina || toX < fromX)
7622                       board[EP_STATUS] = toX | berolina;
7623                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7624                   gameInfo.variant != VariantBerolina || toX > fromX) 
7625                  board[EP_STATUS] = toX;
7626            }
7627       } else
7628       if( board[fromY][fromX] == BlackPawn ) {
7629            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7630                board[EP_STATUS] = EP_PAWN_MOVE; 
7631            if( toY-fromY== -2) {
7632                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7633                         gameInfo.variant != VariantBerolina || toX < fromX)
7634                       board[EP_STATUS] = toX | berolina;
7635                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7636                         gameInfo.variant != VariantBerolina || toX > fromX) 
7637                       board[EP_STATUS] = toX;
7638            }
7639        }
7640
7641        for(i=0; i<nrCastlingRights; i++) {
7642            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7643               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7644              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7645        }
7646
7647     }
7648
7649   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7650   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7651        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7652
7653   if (fromX == toX && fromY == toY) return;
7654
7655   if (fromY == DROP_RANK) {
7656         /* must be first */
7657         piece = board[toY][toX] = (ChessSquare) fromX;
7658   } else {
7659      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7660      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7661      if(gameInfo.variant == VariantKnightmate)
7662          king += (int) WhiteUnicorn - (int) WhiteKing;
7663
7664     /* Code added by Tord: */
7665     /* FRC castling assumed when king captures friendly rook. */
7666     if (board[fromY][fromX] == WhiteKing &&
7667              board[toY][toX] == WhiteRook) {
7668       board[fromY][fromX] = EmptySquare;
7669       board[toY][toX] = EmptySquare;
7670       if(toX > fromX) {
7671         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7672       } else {
7673         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7674       }
7675     } else if (board[fromY][fromX] == BlackKing &&
7676                board[toY][toX] == BlackRook) {
7677       board[fromY][fromX] = EmptySquare;
7678       board[toY][toX] = EmptySquare;
7679       if(toX > fromX) {
7680         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7681       } else {
7682         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7683       }
7684     /* End of code added by Tord */
7685
7686     } else if (board[fromY][fromX] == king
7687         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7688         && toY == fromY && toX > fromX+1) {
7689         board[fromY][fromX] = EmptySquare;
7690         board[toY][toX] = king;
7691         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7692         board[fromY][BOARD_RGHT-1] = EmptySquare;
7693     } else if (board[fromY][fromX] == king
7694         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7695                && toY == fromY && toX < fromX-1) {
7696         board[fromY][fromX] = EmptySquare;
7697         board[toY][toX] = king;
7698         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7699         board[fromY][BOARD_LEFT] = EmptySquare;
7700     } else if (board[fromY][fromX] == WhitePawn
7701                && toY >= BOARD_HEIGHT-promoRank
7702                && gameInfo.variant != VariantXiangqi
7703                ) {
7704         /* white pawn promotion */
7705         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7706         if (board[toY][toX] == EmptySquare) {
7707             board[toY][toX] = WhiteQueen;
7708         }
7709         if(gameInfo.variant==VariantBughouse ||
7710            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7711             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7712         board[fromY][fromX] = EmptySquare;
7713     } else if ((fromY == BOARD_HEIGHT-4)
7714                && (toX != fromX)
7715                && gameInfo.variant != VariantXiangqi
7716                && gameInfo.variant != VariantBerolina
7717                && (board[fromY][fromX] == WhitePawn)
7718                && (board[toY][toX] == EmptySquare)) {
7719         board[fromY][fromX] = EmptySquare;
7720         board[toY][toX] = WhitePawn;
7721         captured = board[toY - 1][toX];
7722         board[toY - 1][toX] = EmptySquare;
7723     } else if ((fromY == BOARD_HEIGHT-4)
7724                && (toX == fromX)
7725                && gameInfo.variant == VariantBerolina
7726                && (board[fromY][fromX] == WhitePawn)
7727                && (board[toY][toX] == EmptySquare)) {
7728         board[fromY][fromX] = EmptySquare;
7729         board[toY][toX] = WhitePawn;
7730         if(oldEP & EP_BEROLIN_A) {
7731                 captured = board[fromY][fromX-1];
7732                 board[fromY][fromX-1] = EmptySquare;
7733         }else{  captured = board[fromY][fromX+1];
7734                 board[fromY][fromX+1] = EmptySquare;
7735         }
7736     } else if (board[fromY][fromX] == king
7737         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7738                && toY == fromY && toX > fromX+1) {
7739         board[fromY][fromX] = EmptySquare;
7740         board[toY][toX] = king;
7741         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7742         board[fromY][BOARD_RGHT-1] = EmptySquare;
7743     } else if (board[fromY][fromX] == king
7744         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7745                && toY == fromY && toX < fromX-1) {
7746         board[fromY][fromX] = EmptySquare;
7747         board[toY][toX] = king;
7748         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7749         board[fromY][BOARD_LEFT] = EmptySquare;
7750     } else if (fromY == 7 && fromX == 3
7751                && board[fromY][fromX] == BlackKing
7752                && toY == 7 && toX == 5) {
7753         board[fromY][fromX] = EmptySquare;
7754         board[toY][toX] = BlackKing;
7755         board[fromY][7] = EmptySquare;
7756         board[toY][4] = BlackRook;
7757     } else if (fromY == 7 && fromX == 3
7758                && board[fromY][fromX] == BlackKing
7759                && toY == 7 && toX == 1) {
7760         board[fromY][fromX] = EmptySquare;
7761         board[toY][toX] = BlackKing;
7762         board[fromY][0] = EmptySquare;
7763         board[toY][2] = BlackRook;
7764     } else if (board[fromY][fromX] == BlackPawn
7765                && toY < promoRank
7766                && gameInfo.variant != VariantXiangqi
7767                ) {
7768         /* black pawn promotion */
7769         board[toY][toX] = CharToPiece(ToLower(promoChar));
7770         if (board[toY][toX] == EmptySquare) {
7771             board[toY][toX] = BlackQueen;
7772         }
7773         if(gameInfo.variant==VariantBughouse ||
7774            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7775             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7776         board[fromY][fromX] = EmptySquare;
7777     } else if ((fromY == 3)
7778                && (toX != fromX)
7779                && gameInfo.variant != VariantXiangqi
7780                && gameInfo.variant != VariantBerolina
7781                && (board[fromY][fromX] == BlackPawn)
7782                && (board[toY][toX] == EmptySquare)) {
7783         board[fromY][fromX] = EmptySquare;
7784         board[toY][toX] = BlackPawn;
7785         captured = board[toY + 1][toX];
7786         board[toY + 1][toX] = EmptySquare;
7787     } else if ((fromY == 3)
7788                && (toX == fromX)
7789                && gameInfo.variant == VariantBerolina
7790                && (board[fromY][fromX] == BlackPawn)
7791                && (board[toY][toX] == EmptySquare)) {
7792         board[fromY][fromX] = EmptySquare;
7793         board[toY][toX] = BlackPawn;
7794         if(oldEP & EP_BEROLIN_A) {
7795                 captured = board[fromY][fromX-1];
7796                 board[fromY][fromX-1] = EmptySquare;
7797         }else{  captured = board[fromY][fromX+1];
7798                 board[fromY][fromX+1] = EmptySquare;
7799         }
7800     } else {
7801         board[toY][toX] = board[fromY][fromX];
7802         board[fromY][fromX] = EmptySquare;
7803     }
7804
7805     /* [HGM] now we promote for Shogi, if needed */
7806     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7807         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7808   }
7809
7810     if (gameInfo.holdingsWidth != 0) {
7811
7812       /* !!A lot more code needs to be written to support holdings  */
7813       /* [HGM] OK, so I have written it. Holdings are stored in the */
7814       /* penultimate board files, so they are automaticlly stored   */
7815       /* in the game history.                                       */
7816       if (fromY == DROP_RANK) {
7817         /* Delete from holdings, by decreasing count */
7818         /* and erasing image if necessary            */
7819         p = (int) fromX;
7820         if(p < (int) BlackPawn) { /* white drop */
7821              p -= (int)WhitePawn;
7822                  p = PieceToNumber((ChessSquare)p);
7823              if(p >= gameInfo.holdingsSize) p = 0;
7824              if(--board[p][BOARD_WIDTH-2] <= 0)
7825                   board[p][BOARD_WIDTH-1] = EmptySquare;
7826              if((int)board[p][BOARD_WIDTH-2] < 0)
7827                         board[p][BOARD_WIDTH-2] = 0;
7828         } else {                  /* black drop */
7829              p -= (int)BlackPawn;
7830                  p = PieceToNumber((ChessSquare)p);
7831              if(p >= gameInfo.holdingsSize) p = 0;
7832              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7833                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7834              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7835                         board[BOARD_HEIGHT-1-p][1] = 0;
7836         }
7837       }
7838       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7839           && gameInfo.variant != VariantBughouse        ) {
7840         /* [HGM] holdings: Add to holdings, if holdings exist */
7841         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7842                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7843                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7844         }
7845         p = (int) captured;
7846         if (p >= (int) BlackPawn) {
7847           p -= (int)BlackPawn;
7848           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7849                   /* in Shogi restore piece to its original  first */
7850                   captured = (ChessSquare) (DEMOTED captured);
7851                   p = DEMOTED p;
7852           }
7853           p = PieceToNumber((ChessSquare)p);
7854           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7855           board[p][BOARD_WIDTH-2]++;
7856           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7857         } else {
7858           p -= (int)WhitePawn;
7859           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7860                   captured = (ChessSquare) (DEMOTED captured);
7861                   p = DEMOTED p;
7862           }
7863           p = PieceToNumber((ChessSquare)p);
7864           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7865           board[BOARD_HEIGHT-1-p][1]++;
7866           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7867         }
7868       }
7869     } else if (gameInfo.variant == VariantAtomic) {
7870       if (captured != EmptySquare) {
7871         int y, x;
7872         for (y = toY-1; y <= toY+1; y++) {
7873           for (x = toX-1; x <= toX+1; x++) {
7874             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7875                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7876               board[y][x] = EmptySquare;
7877             }
7878           }
7879         }
7880         board[toY][toX] = EmptySquare;
7881       }
7882     }
7883     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7884         /* [HGM] Shogi promotions */
7885         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7886     }
7887
7888     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7889                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7890         // [HGM] superchess: take promotion piece out of holdings
7891         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7892         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7893             if(!--board[k][BOARD_WIDTH-2])
7894                 board[k][BOARD_WIDTH-1] = EmptySquare;
7895         } else {
7896             if(!--board[BOARD_HEIGHT-1-k][1])
7897                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7898         }
7899     }
7900
7901 }
7902
7903 /* Updates forwardMostMove */
7904 void
7905 MakeMove(fromX, fromY, toX, toY, promoChar)
7906      int fromX, fromY, toX, toY;
7907      int promoChar;
7908 {
7909 //    forwardMostMove++; // [HGM] bare: moved downstream
7910
7911     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7912         int timeLeft; static int lastLoadFlag=0; int king, piece;
7913         piece = boards[forwardMostMove][fromY][fromX];
7914         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7915         if(gameInfo.variant == VariantKnightmate)
7916             king += (int) WhiteUnicorn - (int) WhiteKing;
7917         if(forwardMostMove == 0) {
7918             if(blackPlaysFirst)
7919                 fprintf(serverMoves, "%s;", second.tidy);
7920             fprintf(serverMoves, "%s;", first.tidy);
7921             if(!blackPlaysFirst)
7922                 fprintf(serverMoves, "%s;", second.tidy);
7923         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7924         lastLoadFlag = loadFlag;
7925         // print base move
7926         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7927         // print castling suffix
7928         if( toY == fromY && piece == king ) {
7929             if(toX-fromX > 1)
7930                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7931             if(fromX-toX >1)
7932                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7933         }
7934         // e.p. suffix
7935         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7936              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7937              boards[forwardMostMove][toY][toX] == EmptySquare
7938              && fromX != toX )
7939                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7940         // promotion suffix
7941         if(promoChar != NULLCHAR)
7942                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7943         if(!loadFlag) {
7944             fprintf(serverMoves, "/%d/%d",
7945                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7946             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7947             else                      timeLeft = blackTimeRemaining/1000;
7948             fprintf(serverMoves, "/%d", timeLeft);
7949         }
7950         fflush(serverMoves);
7951     }
7952
7953     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7954       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7955                         0, 1);
7956       return;
7957     }
7958     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7959     if (commentList[forwardMostMove+1] != NULL) {
7960         free(commentList[forwardMostMove+1]);
7961         commentList[forwardMostMove+1] = NULL;
7962     }
7963     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7964     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7965     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7966     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7967     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7968     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7969     gameInfo.result = GameUnfinished;
7970     if (gameInfo.resultDetails != NULL) {
7971         free(gameInfo.resultDetails);
7972         gameInfo.resultDetails = NULL;
7973     }
7974     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7975                               moveList[forwardMostMove - 1]);
7976     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7977                              PosFlags(forwardMostMove - 1),
7978                              fromY, fromX, toY, toX, promoChar,
7979                              parseList[forwardMostMove - 1]);
7980     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7981       case MT_NONE:
7982       case MT_STALEMATE:
7983       default:
7984         break;
7985       case MT_CHECK:
7986         if(gameInfo.variant != VariantShogi)
7987             strcat(parseList[forwardMostMove - 1], "+");
7988         break;
7989       case MT_CHECKMATE:
7990       case MT_STAINMATE:
7991         strcat(parseList[forwardMostMove - 1], "#");
7992         break;
7993     }
7994     if (appData.debugMode) {
7995         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7996     }
7997
7998 }
7999
8000 /* Updates currentMove if not pausing */
8001 void
8002 ShowMove(fromX, fromY, toX, toY)
8003 {
8004     int instant = (gameMode == PlayFromGameFile) ?
8005         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8006
8007     if(appData.noGUI) return;
8008
8009     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
8010       {
8011         if (!instant)
8012           {
8013             if (forwardMostMove == currentMove + 1)
8014               {
8015 //TODO
8016 //              AnimateMove(boards[forwardMostMove - 1],
8017 //                          fromX, fromY, toX, toY);
8018               }
8019             if (appData.highlightLastMove)
8020               {
8021                 SetHighlights(fromX, fromY, toX, toY);
8022               }
8023           }
8024         currentMove = forwardMostMove;
8025     }
8026
8027     if (instant) return;
8028
8029     DisplayMove(currentMove - 1);
8030     DrawPosition(FALSE, boards[currentMove]);
8031     DisplayBothClocks();
8032     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8033
8034     return;
8035 }
8036
8037 void SendEgtPath(ChessProgramState *cps)
8038 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8039         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8040
8041         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8042
8043         while(*p) {
8044             char c, *q = name+1, *r, *s;
8045
8046             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8047             while(*p && *p != ',') *q++ = *p++;
8048             *q++ = ':'; *q = 0;
8049             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8050                 strcmp(name, ",nalimov:") == 0 ) {
8051                 // take nalimov path from the menu-changeable option first, if it is defined
8052                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8053                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8054             } else
8055             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8056                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8057                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8058                 s = r = StrStr(s, ":") + 1; // beginning of path info
8059                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8060                 c = *r; *r = 0;             // temporarily null-terminate path info
8061                     *--q = 0;               // strip of trailig ':' from name
8062                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8063                 *r = c;
8064                 SendToProgram(buf,cps);     // send egtbpath command for this format
8065             }
8066             if(*p == ',') p++; // read away comma to position for next format name
8067         }
8068 }
8069
8070 void
8071 InitChessProgram(cps, setup)
8072      ChessProgramState *cps;
8073      int setup; /* [HGM] needed to setup FRC opening position */
8074 {
8075     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8076     if (appData.noChessProgram) return;
8077     hintRequested = FALSE;
8078     bookRequested = FALSE;
8079
8080     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8081     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8082     if(cps->memSize) { /* [HGM] memory */
8083         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8084         SendToProgram(buf, cps);
8085     }
8086     SendEgtPath(cps); /* [HGM] EGT */
8087     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8088         sprintf(buf, "cores %d\n", appData.smpCores);
8089         SendToProgram(buf, cps);
8090     }
8091
8092     SendToProgram(cps->initString, cps);
8093     if (gameInfo.variant != VariantNormal &&
8094         gameInfo.variant != VariantLoadable
8095         /* [HGM] also send variant if board size non-standard */
8096         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8097                                             ) {
8098       char *v = VariantName(gameInfo.variant);
8099       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8100         /* [HGM] in protocol 1 we have to assume all variants valid */
8101         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8102         DisplayFatalError(buf, 0, 1);
8103         return;
8104       }
8105
8106       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8107       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8108       if( gameInfo.variant == VariantXiangqi )
8109            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8110       if( gameInfo.variant == VariantShogi )
8111            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8112       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8113            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8114       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8115                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8116            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8117       if( gameInfo.variant == VariantCourier )
8118            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8119       if( gameInfo.variant == VariantSuper )
8120            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8121       if( gameInfo.variant == VariantGreat )
8122            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8123
8124       if(overruled) {
8125            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8126                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8127            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8128            if(StrStr(cps->variants, b) == NULL) {
8129                // specific sized variant not known, check if general sizing allowed
8130                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8131                    if(StrStr(cps->variants, "boardsize") == NULL) {
8132                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8133                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8134                        DisplayFatalError(buf, 0, 1);
8135                        return;
8136                    }
8137                    /* [HGM] here we really should compare with the maximum supported board size */
8138                }
8139            }
8140       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8141       sprintf(buf, "variant %s\n", b);
8142       SendToProgram(buf, cps);
8143     }
8144     currentlyInitializedVariant = gameInfo.variant;
8145
8146     /* [HGM] send opening position in FRC to first engine */
8147     if(setup) {
8148           SendToProgram("force\n", cps);
8149           SendBoard(cps, 0);
8150           /* engine is now in force mode! Set flag to wake it up after first move. */
8151           setboardSpoiledMachineBlack = 1;
8152     }
8153
8154     if (cps->sendICS) {
8155       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8156       SendToProgram(buf, cps);
8157     }
8158     cps->maybeThinking = FALSE;
8159     cps->offeredDraw = 0;
8160     if (!appData.icsActive) {
8161         SendTimeControl(cps, movesPerSession, timeControl,
8162                         timeIncrement, appData.searchDepth,
8163                         searchTime);
8164     }
8165     if (appData.showThinking
8166         // [HGM] thinking: four options require thinking output to be sent
8167         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8168                                 ) {
8169         SendToProgram("post\n", cps);
8170     }
8171     SendToProgram("hard\n", cps);
8172     if (!appData.ponderNextMove) {
8173         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8174            it without being sure what state we are in first.  "hard"
8175            is not a toggle, so that one is OK.
8176          */
8177         SendToProgram("easy\n", cps);
8178     }
8179     if (cps->usePing) {
8180       sprintf(buf, "ping %d\n", ++cps->lastPing);
8181       SendToProgram(buf, cps);
8182     }
8183     cps->initDone = TRUE;
8184 }
8185
8186
8187 void
8188 StartChessProgram(cps)
8189      ChessProgramState *cps;
8190 {
8191     char buf[MSG_SIZ];
8192     int err;
8193
8194     if (appData.noChessProgram) return;
8195     cps->initDone = FALSE;
8196
8197     if (strcmp(cps->host, "localhost") == 0) {
8198         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8199     } else if (*appData.remoteShell == NULLCHAR) {
8200         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8201     } else {
8202         if (*appData.remoteUser == NULLCHAR) {
8203           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8204                     cps->program);
8205         } else {
8206           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8207                     cps->host, appData.remoteUser, cps->program);
8208         }
8209         err = StartChildProcess(buf, "", &cps->pr);
8210     }
8211
8212     if (err != 0) {
8213         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8214         DisplayFatalError(buf, err, 1);
8215         cps->pr = NoProc;
8216         cps->isr = NULL;
8217         return;
8218     }
8219
8220     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8221     if (cps->protocolVersion > 1) {
8222       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8223       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8224       cps->comboCnt = 0;  //                and values of combo boxes
8225       SendToProgram(buf, cps);
8226     } else {
8227       SendToProgram("xboard\n", cps);
8228     }
8229 }
8230
8231
8232 void
8233 TwoMachinesEventIfReady P((void))
8234 {
8235   if (first.lastPing != first.lastPong) {
8236     DisplayMessage("", _("Waiting for first chess program"));
8237     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8238     return;
8239   }
8240   if (second.lastPing != second.lastPong) {
8241     DisplayMessage("", _("Waiting for second chess program"));
8242     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8243     return;
8244   }
8245   ThawUI();
8246   TwoMachinesEvent();
8247 }
8248
8249 void
8250 NextMatchGame P((void))
8251 {
8252     int index; /* [HGM] autoinc: step load index during match */
8253     Reset(FALSE, TRUE);
8254     if (*appData.loadGameFile != NULLCHAR) {
8255         index = appData.loadGameIndex;
8256         if(index < 0) { // [HGM] autoinc
8257             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8258             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8259         }
8260         LoadGameFromFile(appData.loadGameFile,
8261                          index,
8262                          appData.loadGameFile, FALSE);
8263     } else if (*appData.loadPositionFile != NULLCHAR) {
8264         index = appData.loadPositionIndex;
8265         if(index < 0) { // [HGM] autoinc
8266             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8267             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8268         }
8269         LoadPositionFromFile(appData.loadPositionFile,
8270                              index,
8271                              appData.loadPositionFile);
8272     }
8273     TwoMachinesEventIfReady();
8274 }
8275
8276 void UserAdjudicationEvent( int result )
8277 {
8278     ChessMove gameResult = GameIsDrawn;
8279
8280     if( result > 0 ) {
8281         gameResult = WhiteWins;
8282     }
8283     else if( result < 0 ) {
8284         gameResult = BlackWins;
8285     }
8286
8287     if( gameMode == TwoMachinesPlay ) {
8288         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8289     }
8290 }
8291
8292
8293 // [HGM] save: calculate checksum of game to make games easily identifiable
8294 int StringCheckSum(char *s)
8295 {
8296         int i = 0;
8297         if(s==NULL) return 0;
8298         while(*s) i = i*259 + *s++;
8299         return i;
8300 }
8301
8302 int GameCheckSum()
8303 {
8304         int i, sum=0;
8305         for(i=backwardMostMove; i<forwardMostMove; i++) {
8306                 sum += pvInfoList[i].depth;
8307                 sum += StringCheckSum(parseList[i]);
8308                 sum += StringCheckSum(commentList[i]);
8309                 sum *= 261;
8310         }
8311         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8312         return sum + StringCheckSum(commentList[i]);
8313 } // end of save patch
8314
8315 void
8316 GameEnds(result, resultDetails, whosays)
8317      ChessMove result;
8318      char *resultDetails;
8319      int whosays;
8320 {
8321     GameMode nextGameMode;
8322     int isIcsGame;
8323     char buf[MSG_SIZ];
8324
8325     if(endingGame) return; /* [HGM] crash: forbid recursion */
8326     endingGame = 1;
8327
8328     if (appData.debugMode) {
8329       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8330               result, resultDetails ? resultDetails : "(null)", whosays);
8331     }
8332
8333     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8334         /* If we are playing on ICS, the server decides when the
8335            game is over, but the engine can offer to draw, claim
8336            a draw, or resign.
8337          */
8338 #if ZIPPY
8339         if (appData.zippyPlay && first.initDone) {
8340             if (result == GameIsDrawn) {
8341                 /* In case draw still needs to be claimed */
8342                 SendToICS(ics_prefix);
8343                 SendToICS("draw\n");
8344             } else if (StrCaseStr(resultDetails, "resign")) {
8345                 SendToICS(ics_prefix);
8346                 SendToICS("resign\n");
8347             }
8348         }
8349 #endif
8350         endingGame = 0; /* [HGM] crash */
8351         return;
8352     }
8353
8354     /* If we're loading the game from a file, stop */
8355     if (whosays == GE_FILE) {
8356       (void) StopLoadGameTimer();
8357       gameFileFP = NULL;
8358     }
8359
8360     /* Cancel draw offers */
8361     first.offeredDraw = second.offeredDraw = 0;
8362
8363     /* If this is an ICS game, only ICS can really say it's done;
8364        if not, anyone can. */
8365     isIcsGame = (gameMode == IcsPlayingWhite ||
8366                  gameMode == IcsPlayingBlack ||
8367                  gameMode == IcsObserving    ||
8368                  gameMode == IcsExamining);
8369
8370     if (!isIcsGame || whosays == GE_ICS) {
8371         /* OK -- not an ICS game, or ICS said it was done */
8372         StopClocks();
8373         if (!isIcsGame && !appData.noChessProgram)
8374           SetUserThinkingEnables();
8375
8376         /* [HGM] if a machine claims the game end we verify this claim */
8377         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8378             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8379                 char claimer;
8380                 ChessMove trueResult = (ChessMove) -1;
8381
8382                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8383                                             first.twoMachinesColor[0] :
8384                                             second.twoMachinesColor[0] ;
8385
8386                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8387                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8388                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8389                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8390                 } else
8391                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8392                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8393                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8394                 } else
8395                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8396                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8397                 }
8398
8399                 // now verify win claims, but not in drop games, as we don't understand those yet
8400                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8401                                                  || gameInfo.variant == VariantGreat) &&
8402                     (result == WhiteWins && claimer == 'w' ||
8403                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8404                       if (appData.debugMode) {
8405                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8406                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8407                       }
8408                       if(result != trueResult) {
8409                               sprintf(buf, "False win claim: '%s'", resultDetails);
8410                               result = claimer == 'w' ? BlackWins : WhiteWins;
8411                               resultDetails = buf;
8412                       }
8413                 } else
8414                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8415                     && (forwardMostMove <= backwardMostMove ||
8416                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8417                         (claimer=='b')==(forwardMostMove&1))
8418                                                                                   ) {
8419                       /* [HGM] verify: draws that were not flagged are false claims */
8420                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8421                       result = claimer == 'w' ? BlackWins : WhiteWins;
8422                       resultDetails = buf;
8423                 }
8424                 /* (Claiming a loss is accepted no questions asked!) */
8425             }
8426
8427             /* [HGM] bare: don't allow bare King to win */
8428             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8429                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8430                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8431                && result != GameIsDrawn)
8432             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8433                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8434                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8435                         if(p >= 0 && p <= (int)WhiteKing) k++;
8436                 }
8437                 if (appData.debugMode) {
8438                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8439                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8440                 }
8441                 if(k <= 1) {
8442                         result = GameIsDrawn;
8443                         sprintf(buf, "%s but bare king", resultDetails);
8444                         resultDetails = buf;
8445                 }
8446             }
8447         }
8448
8449         if(serverMoves != NULL && !loadFlag) { char c = '=';
8450             if(result==WhiteWins) c = '+';
8451             if(result==BlackWins) c = '-';
8452             if(resultDetails != NULL)
8453                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8454         }
8455         if (resultDetails != NULL) {
8456             gameInfo.result = result;
8457             gameInfo.resultDetails = StrSave(resultDetails);
8458
8459             /* display last move only if game was not loaded from file */
8460             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8461                 DisplayMove(currentMove - 1);
8462
8463             if (forwardMostMove != 0) {
8464                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8465                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8466                                                                 ) {
8467                     if (*appData.saveGameFile != NULLCHAR) {
8468                         SaveGameToFile(appData.saveGameFile, TRUE);
8469                     } else if (appData.autoSaveGames) {
8470                         AutoSaveGame();
8471                     }
8472                     if (*appData.savePositionFile != NULLCHAR) {
8473                         SavePositionToFile(appData.savePositionFile);
8474                     }
8475                 }
8476             }
8477
8478             /* Tell program how game ended in case it is learning */
8479             /* [HGM] Moved this to after saving the PGN, just in case */
8480             /* engine died and we got here through time loss. In that */
8481             /* case we will get a fatal error writing the pipe, which */
8482             /* would otherwise lose us the PGN.                       */
8483             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8484             /* output during GameEnds should never be fatal anymore   */
8485             if (gameMode == MachinePlaysWhite ||
8486                 gameMode == MachinePlaysBlack ||
8487                 gameMode == TwoMachinesPlay ||
8488                 gameMode == IcsPlayingWhite ||
8489                 gameMode == IcsPlayingBlack ||
8490                 gameMode == BeginningOfGame) {
8491                 char buf[MSG_SIZ];
8492                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8493                         resultDetails);
8494                 if (first.pr != NoProc) {
8495                     SendToProgram(buf, &first);
8496                 }
8497                 if (second.pr != NoProc &&
8498                     gameMode == TwoMachinesPlay) {
8499                     SendToProgram(buf, &second);
8500                 }
8501             }
8502         }
8503
8504         if (appData.icsActive) {
8505             if (appData.quietPlay &&
8506                 (gameMode == IcsPlayingWhite ||
8507                  gameMode == IcsPlayingBlack)) {
8508                 SendToICS(ics_prefix);
8509                 SendToICS("set shout 1\n");
8510             }
8511             nextGameMode = IcsIdle;
8512             ics_user_moved = FALSE;
8513             /* clean up premove.  It's ugly when the game has ended and the
8514              * premove highlights are still on the board.
8515              */
8516             if (gotPremove) {
8517               gotPremove = FALSE;
8518               ClearPremoveHighlights();
8519               DrawPosition(FALSE, boards[currentMove]);
8520             }
8521             if (whosays == GE_ICS) {
8522                 switch (result) {
8523                 case WhiteWins:
8524                     if (gameMode == IcsPlayingWhite)
8525                         PlayIcsWinSound();
8526                     else if(gameMode == IcsPlayingBlack)
8527                         PlayIcsLossSound();
8528                     break;
8529                 case BlackWins:
8530                     if (gameMode == IcsPlayingBlack)
8531                         PlayIcsWinSound();
8532                     else if(gameMode == IcsPlayingWhite)
8533                         PlayIcsLossSound();
8534                     break;
8535                 case GameIsDrawn:
8536                     PlayIcsDrawSound();
8537                     break;
8538                 default:
8539                     PlayIcsUnfinishedSound();
8540                 }
8541             }
8542         } else if (gameMode == EditGame ||
8543                    gameMode == PlayFromGameFile ||
8544                    gameMode == AnalyzeMode ||
8545                    gameMode == AnalyzeFile) {
8546             nextGameMode = gameMode;
8547         } else {
8548             nextGameMode = EndOfGame;
8549         }
8550         pausing = FALSE;
8551         ModeHighlight();
8552     } else {
8553         nextGameMode = gameMode;
8554     }
8555
8556     if (appData.noChessProgram) {
8557         gameMode = nextGameMode;
8558         ModeHighlight();
8559         endingGame = 0; /* [HGM] crash */
8560         return;
8561     }
8562
8563     if (first.reuse) {
8564         /* Put first chess program into idle state */
8565         if (first.pr != NoProc &&
8566             (gameMode == MachinePlaysWhite ||
8567              gameMode == MachinePlaysBlack ||
8568              gameMode == TwoMachinesPlay ||
8569              gameMode == IcsPlayingWhite ||
8570              gameMode == IcsPlayingBlack ||
8571              gameMode == BeginningOfGame)) {
8572             SendToProgram("force\n", &first);
8573             if (first.usePing) {
8574               char buf[MSG_SIZ];
8575               sprintf(buf, "ping %d\n", ++first.lastPing);
8576               SendToProgram(buf, &first);
8577             }
8578         }
8579     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8580         /* Kill off first chess program */
8581         if (first.isr != NULL)
8582           RemoveInputSource(first.isr);
8583         first.isr = NULL;
8584
8585         if (first.pr != NoProc) {
8586             ExitAnalyzeMode();
8587             DoSleep( appData.delayBeforeQuit );
8588             SendToProgram("quit\n", &first);
8589             DoSleep( appData.delayAfterQuit );
8590             DestroyChildProcess(first.pr, first.useSigterm);
8591         }
8592         first.pr = NoProc;
8593     }
8594     if (second.reuse) {
8595         /* Put second chess program into idle state */
8596         if (second.pr != NoProc &&
8597             gameMode == TwoMachinesPlay) {
8598             SendToProgram("force\n", &second);
8599             if (second.usePing) {
8600               char buf[MSG_SIZ];
8601               sprintf(buf, "ping %d\n", ++second.lastPing);
8602               SendToProgram(buf, &second);
8603             }
8604         }
8605     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8606         /* Kill off second chess program */
8607         if (second.isr != NULL)
8608           RemoveInputSource(second.isr);
8609         second.isr = NULL;
8610
8611         if (second.pr != NoProc) {
8612             DoSleep( appData.delayBeforeQuit );
8613             SendToProgram("quit\n", &second);
8614             DoSleep( appData.delayAfterQuit );
8615             DestroyChildProcess(second.pr, second.useSigterm);
8616         }
8617         second.pr = NoProc;
8618     }
8619
8620     if (matchMode && gameMode == TwoMachinesPlay) {
8621         switch (result) {
8622         case WhiteWins:
8623           if (first.twoMachinesColor[0] == 'w') {
8624             first.matchWins++;
8625           } else {
8626             second.matchWins++;
8627           }
8628           break;
8629         case BlackWins:
8630           if (first.twoMachinesColor[0] == 'b') {
8631             first.matchWins++;
8632           } else {
8633             second.matchWins++;
8634           }
8635           break;
8636         default:
8637           break;
8638         }
8639         if (matchGame < appData.matchGames) {
8640             char *tmp;
8641             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8642                 tmp = first.twoMachinesColor;
8643                 first.twoMachinesColor = second.twoMachinesColor;
8644                 second.twoMachinesColor = tmp;
8645             }
8646             gameMode = nextGameMode;
8647             matchGame++;
8648             if(appData.matchPause>10000 || appData.matchPause<10)
8649                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8650             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8651             endingGame = 0; /* [HGM] crash */
8652             return;
8653         } else {
8654             char buf[MSG_SIZ];
8655             gameMode = nextGameMode;
8656             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8657                     first.tidy, second.tidy,
8658                     first.matchWins, second.matchWins,
8659                     appData.matchGames - (first.matchWins + second.matchWins));
8660             DisplayFatalError(buf, 0, 0);
8661         }
8662     }
8663     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8664         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8665       ExitAnalyzeMode();
8666     gameMode = nextGameMode;
8667     ModeHighlight();
8668     endingGame = 0;  /* [HGM] crash */
8669 }
8670
8671 /* Assumes program was just initialized (initString sent).
8672    Leaves program in force mode. */
8673 void
8674 FeedMovesToProgram(cps, upto)
8675      ChessProgramState *cps;
8676      int upto;
8677 {
8678     int i;
8679
8680     if (appData.debugMode)
8681       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8682               startedFromSetupPosition ? "position and " : "",
8683               backwardMostMove, upto, cps->which);
8684     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8685         // [HGM] variantswitch: make engine aware of new variant
8686         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8687                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8688         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8689         SendToProgram(buf, cps);
8690         currentlyInitializedVariant = gameInfo.variant;
8691     }
8692     SendToProgram("force\n", cps);
8693     if (startedFromSetupPosition) {
8694         SendBoard(cps, backwardMostMove);
8695     if (appData.debugMode) {
8696         fprintf(debugFP, "feedMoves\n");
8697     }
8698     }
8699     for (i = backwardMostMove; i < upto; i++) {
8700         SendMoveToProgram(i, cps);
8701     }
8702 }
8703
8704
8705 void
8706 ResurrectChessProgram()
8707 {
8708      /* The chess program may have exited.
8709         If so, restart it and feed it all the moves made so far. */
8710
8711     if (appData.noChessProgram || first.pr != NoProc) return;
8712
8713     StartChessProgram(&first);
8714     InitChessProgram(&first, FALSE);
8715     FeedMovesToProgram(&first, currentMove);
8716
8717     if (!first.sendTime) {
8718         /* can't tell gnuchess what its clock should read,
8719            so we bow to its notion. */
8720         ResetClocks();
8721         timeRemaining[0][currentMove] = whiteTimeRemaining;
8722         timeRemaining[1][currentMove] = blackTimeRemaining;
8723     }
8724
8725     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8726                 appData.icsEngineAnalyze) && first.analysisSupport) {
8727       SendToProgram("analyze\n", &first);
8728       first.analyzing = TRUE;
8729     }
8730 }
8731
8732 /*
8733  * Button procedures
8734  */
8735 void
8736 Reset(redraw, init)
8737      int redraw, init;
8738 {
8739     int i;
8740
8741     if (appData.debugMode) {
8742         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8743                 redraw, init, gameMode);
8744     }
8745     CleanupTail(); // [HGM] vari: delete any stored variations
8746     pausing = pauseExamInvalid = FALSE;
8747     startedFromSetupPosition = blackPlaysFirst = FALSE;
8748     firstMove = TRUE;
8749     whiteFlag = blackFlag = FALSE;
8750     userOfferedDraw = FALSE;
8751     hintRequested = bookRequested = FALSE;
8752     first.maybeThinking = FALSE;
8753     second.maybeThinking = FALSE;
8754     first.bookSuspend = FALSE; // [HGM] book
8755     second.bookSuspend = FALSE;
8756     thinkOutput[0] = NULLCHAR;
8757     lastHint[0] = NULLCHAR;
8758     ClearGameInfo(&gameInfo);
8759     gameInfo.variant = StringToVariant(appData.variant);
8760     ics_user_moved = ics_clock_paused = FALSE;
8761     ics_getting_history = H_FALSE;
8762     ics_gamenum = -1;
8763     white_holding[0] = black_holding[0] = NULLCHAR;
8764     ClearProgramStats();
8765     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8766
8767     ResetFrontEnd();
8768     ClearHighlights();
8769     flipView = appData.flipView;
8770     ClearPremoveHighlights();
8771     gotPremove = FALSE;
8772     alarmSounded = FALSE;
8773
8774     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8775     if(appData.serverMovesName != NULL) {
8776         /* [HGM] prepare to make moves file for broadcasting */
8777         clock_t t = clock();
8778         if(serverMoves != NULL) fclose(serverMoves);
8779         serverMoves = fopen(appData.serverMovesName, "r");
8780         if(serverMoves != NULL) {
8781             fclose(serverMoves);
8782             /* delay 15 sec before overwriting, so all clients can see end */
8783             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8784         }
8785         serverMoves = fopen(appData.serverMovesName, "w");
8786     }
8787
8788     ExitAnalyzeMode();
8789     gameMode = BeginningOfGame;
8790     ModeHighlight();
8791
8792     if(appData.icsActive) gameInfo.variant = VariantNormal;
8793     currentMove = forwardMostMove = backwardMostMove = 0;
8794     InitPosition(redraw);
8795     for (i = 0; i < MAX_MOVES; i++) {
8796         if (commentList[i] != NULL) {
8797             free(commentList[i]);
8798             commentList[i] = NULL;
8799         }
8800     }
8801
8802     ResetClocks();
8803     timeRemaining[0][0] = whiteTimeRemaining;
8804     timeRemaining[1][0] = blackTimeRemaining;
8805     if (first.pr == NULL) {
8806         StartChessProgram(&first);
8807     }
8808     if (init) {
8809             InitChessProgram(&first, startedFromSetupPosition);
8810     }
8811
8812     DisplayTitle("");
8813     DisplayMessage("", "");
8814     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8815     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8816     return;
8817 }
8818
8819 void
8820 AutoPlayGameLoop()
8821 {
8822     for (;;) {
8823         if (!AutoPlayOneMove())
8824           return;
8825         if (matchMode || appData.timeDelay == 0)
8826           continue;
8827         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8828           return;
8829         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8830         break;
8831     }
8832 }
8833
8834
8835 int
8836 AutoPlayOneMove()
8837 {
8838     int fromX, fromY, toX, toY;
8839
8840     if (appData.debugMode) {
8841       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8842     }
8843
8844     if (gameMode != PlayFromGameFile)
8845       return FALSE;
8846
8847     if (currentMove >= forwardMostMove) {
8848       gameMode = EditGame;
8849       ModeHighlight();
8850
8851       /* [AS] Clear current move marker at the end of a game */
8852       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8853
8854       return FALSE;
8855     }
8856
8857     toX = moveList[currentMove][2] - AAA;
8858     toY = moveList[currentMove][3] - ONE;
8859
8860     if (moveList[currentMove][1] == '@') {
8861         if (appData.highlightLastMove) {
8862             SetHighlights(-1, -1, toX, toY);
8863         }
8864     } else {
8865         fromX = moveList[currentMove][0] - AAA;
8866         fromY = moveList[currentMove][1] - ONE;
8867
8868         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8869
8870         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8871
8872         if (appData.highlightLastMove) {
8873             SetHighlights(fromX, fromY, toX, toY);
8874         }
8875     }
8876     DisplayMove(currentMove);
8877     SendMoveToProgram(currentMove++, &first);
8878     DisplayBothClocks();
8879     DrawPosition(FALSE, boards[currentMove]);
8880     // [HGM] PV info: always display, routine tests if empty
8881     DisplayComment(currentMove - 1, commentList[currentMove]);
8882     return TRUE;
8883 }
8884
8885
8886 int
8887 LoadGameOneMove(readAhead)
8888      ChessMove readAhead;
8889 {
8890     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8891     char promoChar = NULLCHAR;
8892     ChessMove moveType;
8893     char move[MSG_SIZ];
8894     char *p, *q;
8895
8896     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8897         gameMode != AnalyzeMode && gameMode != Training) {
8898         gameFileFP = NULL;
8899         return FALSE;
8900     }
8901
8902     yyboardindex = forwardMostMove;
8903     if (readAhead != (ChessMove)0) {
8904       moveType = readAhead;
8905     } else {
8906       if (gameFileFP == NULL)
8907           return FALSE;
8908       moveType = (ChessMove) yylex();
8909     }
8910
8911     done = FALSE;
8912     switch (moveType) {
8913       case Comment:
8914         if (appData.debugMode)
8915           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8916         p = yy_text;
8917
8918         /* append the comment but don't display it */
8919         AppendComment(currentMove, p, FALSE);
8920         return TRUE;
8921
8922       case WhiteCapturesEnPassant:
8923       case BlackCapturesEnPassant:
8924       case WhitePromotionChancellor:
8925       case BlackPromotionChancellor:
8926       case WhitePromotionArchbishop:
8927       case BlackPromotionArchbishop:
8928       case WhitePromotionCentaur:
8929       case BlackPromotionCentaur:
8930       case WhitePromotionQueen:
8931       case BlackPromotionQueen:
8932       case WhitePromotionRook:
8933       case BlackPromotionRook:
8934       case WhitePromotionBishop:
8935       case BlackPromotionBishop:
8936       case WhitePromotionKnight:
8937       case BlackPromotionKnight:
8938       case WhitePromotionKing:
8939       case BlackPromotionKing:
8940       case NormalMove:
8941       case WhiteKingSideCastle:
8942       case WhiteQueenSideCastle:
8943       case BlackKingSideCastle:
8944       case BlackQueenSideCastle:
8945       case WhiteKingSideCastleWild:
8946       case WhiteQueenSideCastleWild:
8947       case BlackKingSideCastleWild:
8948       case BlackQueenSideCastleWild:
8949       /* PUSH Fabien */
8950       case WhiteHSideCastleFR:
8951       case WhiteASideCastleFR:
8952       case BlackHSideCastleFR:
8953       case BlackASideCastleFR:
8954       /* POP Fabien */
8955         if (appData.debugMode)
8956           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8957         fromX = currentMoveString[0] - AAA;
8958         fromY = currentMoveString[1] - ONE;
8959         toX = currentMoveString[2] - AAA;
8960         toY = currentMoveString[3] - ONE;
8961         promoChar = currentMoveString[4];
8962         break;
8963
8964       case WhiteDrop:
8965       case BlackDrop:
8966         if (appData.debugMode)
8967           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8968         fromX = moveType == WhiteDrop ?
8969           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8970         (int) CharToPiece(ToLower(currentMoveString[0]));
8971         fromY = DROP_RANK;
8972         toX = currentMoveString[2] - AAA;
8973         toY = currentMoveString[3] - ONE;
8974         break;
8975
8976       case WhiteWins:
8977       case BlackWins:
8978       case GameIsDrawn:
8979       case GameUnfinished:
8980         if (appData.debugMode)
8981           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8982         p = strchr(yy_text, '{');
8983         if (p == NULL) p = strchr(yy_text, '(');
8984         if (p == NULL) {
8985             p = yy_text;
8986             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8987         } else {
8988             q = strchr(p, *p == '{' ? '}' : ')');
8989             if (q != NULL) *q = NULLCHAR;
8990             p++;
8991         }
8992         GameEnds(moveType, p, GE_FILE);
8993         done = TRUE;
8994         if (cmailMsgLoaded) {
8995             ClearHighlights();
8996             flipView = WhiteOnMove(currentMove);
8997             if (moveType == GameUnfinished) flipView = !flipView;
8998             if (appData.debugMode)
8999               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9000         }
9001         break;
9002
9003       case (ChessMove) 0:       /* end of file */
9004         if (appData.debugMode)
9005           fprintf(debugFP, "Parser hit end of file\n");
9006         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9007           case MT_NONE:
9008           case MT_CHECK:
9009             break;
9010           case MT_CHECKMATE:
9011           case MT_STAINMATE:
9012             if (WhiteOnMove(currentMove)) {
9013                 GameEnds(BlackWins, "Black mates", GE_FILE);
9014             } else {
9015                 GameEnds(WhiteWins, "White mates", GE_FILE);
9016             }
9017             break;
9018           case MT_STALEMATE:
9019             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9020             break;
9021         }
9022         done = TRUE;
9023         break;
9024
9025       case MoveNumberOne:
9026         if (lastLoadGameStart == GNUChessGame) {
9027             /* GNUChessGames have numbers, but they aren't move numbers */
9028             if (appData.debugMode)
9029               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9030                       yy_text, (int) moveType);
9031             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9032         }
9033         /* else fall thru */
9034
9035       case XBoardGame:
9036       case GNUChessGame:
9037       case PGNTag:
9038         /* Reached start of next game in file */
9039         if (appData.debugMode)
9040           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9041         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9042           case MT_NONE:
9043           case MT_CHECK:
9044             break;
9045           case MT_CHECKMATE:
9046           case MT_STAINMATE:
9047             if (WhiteOnMove(currentMove)) {
9048                 GameEnds(BlackWins, "Black mates", GE_FILE);
9049             } else {
9050                 GameEnds(WhiteWins, "White mates", GE_FILE);
9051             }
9052             break;
9053           case MT_STALEMATE:
9054             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9055             break;
9056         }
9057         done = TRUE;
9058         break;
9059
9060       case PositionDiagram:     /* should not happen; ignore */
9061       case ElapsedTime:         /* ignore */
9062       case NAG:                 /* ignore */
9063         if (appData.debugMode)
9064           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9065                   yy_text, (int) moveType);
9066         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9067
9068       case IllegalMove:
9069         if (appData.testLegality) {
9070             if (appData.debugMode)
9071               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9072             sprintf(move, _("Illegal move: %d.%s%s"),
9073                     (forwardMostMove / 2) + 1,
9074                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9075             DisplayError(move, 0);
9076             done = TRUE;
9077         } else {
9078             if (appData.debugMode)
9079               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9080                       yy_text, currentMoveString);
9081             fromX = currentMoveString[0] - AAA;
9082             fromY = currentMoveString[1] - ONE;
9083             toX = currentMoveString[2] - AAA;
9084             toY = currentMoveString[3] - ONE;
9085             promoChar = currentMoveString[4];
9086         }
9087         break;
9088
9089       case AmbiguousMove:
9090         if (appData.debugMode)
9091           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9092         sprintf(move, _("Ambiguous move: %d.%s%s"),
9093                 (forwardMostMove / 2) + 1,
9094                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9095         DisplayError(move, 0);
9096         done = TRUE;
9097         break;
9098
9099       default:
9100       case ImpossibleMove:
9101         if (appData.debugMode)
9102           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9103         sprintf(move, _("Illegal move: %d.%s%s"),
9104                 (forwardMostMove / 2) + 1,
9105                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9106         DisplayError(move, 0);
9107         done = TRUE;
9108         break;
9109     }
9110
9111     if (done) {
9112         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9113             DrawPosition(FALSE, boards[currentMove]);
9114             DisplayBothClocks();
9115             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9116               DisplayComment(currentMove - 1, commentList[currentMove]);
9117         }
9118         (void) StopLoadGameTimer();
9119         gameFileFP = NULL;
9120         cmailOldMove = forwardMostMove;
9121         return FALSE;
9122     } else {
9123         /* currentMoveString is set as a side-effect of yylex */
9124         strcat(currentMoveString, "\n");
9125         strcpy(moveList[forwardMostMove], currentMoveString);
9126
9127         thinkOutput[0] = NULLCHAR;
9128         MakeMove(fromX, fromY, toX, toY, promoChar);
9129         currentMove = forwardMostMove;
9130         return TRUE;
9131     }
9132 }
9133
9134 /* Load the nth game from the given file */
9135 int
9136 LoadGameFromFile(filename, n, title, useList)
9137      char *filename;
9138      int n;
9139      char *title;
9140      /*Boolean*/ int useList;
9141 {
9142     FILE *f;
9143     char buf[MSG_SIZ];
9144
9145     if (strcmp(filename, "-") == 0) {
9146         f = stdin;
9147         title = "stdin";
9148     } else {
9149         f = fopen(filename, "rb");
9150         if (f == NULL) {
9151           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9152             DisplayError(buf, errno);
9153             return FALSE;
9154         }
9155     }
9156     if (fseek(f, 0, 0) == -1) {
9157         /* f is not seekable; probably a pipe */
9158         useList = FALSE;
9159     }
9160     if (useList && n == 0) {
9161         int error = GameListBuild(f);
9162         if (error) {
9163             DisplayError(_("Cannot build game list"), error);
9164         } else if (!ListEmpty(&gameList) &&
9165                    ((ListGame *) gameList.tailPred)->number > 1) {
9166           // TODO convert to GTK
9167           //        GameListPopUp(f, title);
9168             return TRUE;
9169         }
9170         GameListDestroy();
9171         n = 1;
9172     }
9173     if (n == 0) n = 1;
9174     return LoadGame(f, n, title, FALSE);
9175 }
9176
9177
9178 void
9179 MakeRegisteredMove()
9180 {
9181     int fromX, fromY, toX, toY;
9182     char promoChar;
9183     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9184         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9185           case CMAIL_MOVE:
9186           case CMAIL_DRAW:
9187             if (appData.debugMode)
9188               fprintf(debugFP, "Restoring %s for game %d\n",
9189                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9190
9191             thinkOutput[0] = NULLCHAR;
9192             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9193             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9194             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9195             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9196             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9197             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9198             MakeMove(fromX, fromY, toX, toY, promoChar);
9199             ShowMove(fromX, fromY, toX, toY);
9200             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9201               case MT_NONE:
9202               case MT_CHECK:
9203                 break;
9204
9205               case MT_CHECKMATE:
9206               case MT_STAINMATE:
9207                 if (WhiteOnMove(currentMove)) {
9208                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9209                 } else {
9210                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9211                 }
9212                 break;
9213
9214               case MT_STALEMATE:
9215                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9216                 break;
9217             }
9218
9219             break;
9220
9221           case CMAIL_RESIGN:
9222             if (WhiteOnMove(currentMove)) {
9223                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9224             } else {
9225                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9226             }
9227             break;
9228
9229           case CMAIL_ACCEPT:
9230             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9231             break;
9232
9233           default:
9234             break;
9235         }
9236     }
9237
9238     return;
9239 }
9240
9241 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9242 int
9243 CmailLoadGame(f, gameNumber, title, useList)
9244      FILE *f;
9245      int gameNumber;
9246      char *title;
9247      int useList;
9248 {
9249     int retVal;
9250
9251     if (gameNumber > nCmailGames) {
9252         DisplayError(_("No more games in this message"), 0);
9253         return FALSE;
9254     }
9255     if (f == lastLoadGameFP) {
9256         int offset = gameNumber - lastLoadGameNumber;
9257         if (offset == 0) {
9258             cmailMsg[0] = NULLCHAR;
9259             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9260                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9261                 nCmailMovesRegistered--;
9262             }
9263             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9264             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9265                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9266             }
9267         } else {
9268             if (! RegisterMove()) return FALSE;
9269         }
9270     }
9271
9272     retVal = LoadGame(f, gameNumber, title, useList);
9273
9274     /* Make move registered during previous look at this game, if any */
9275     MakeRegisteredMove();
9276
9277     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9278         commentList[currentMove]
9279           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9280         DisplayComment(currentMove - 1, commentList[currentMove]);
9281     }
9282
9283     return retVal;
9284 }
9285
9286 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9287 int
9288 ReloadGame(offset)
9289      int offset;
9290 {
9291     int gameNumber = lastLoadGameNumber + offset;
9292     if (lastLoadGameFP == NULL) {
9293         DisplayError(_("No game has been loaded yet"), 0);
9294         return FALSE;
9295     }
9296     if (gameNumber <= 0) {
9297         DisplayError(_("Can't back up any further"), 0);
9298         return FALSE;
9299     }
9300     if (cmailMsgLoaded) {
9301         return CmailLoadGame(lastLoadGameFP, gameNumber,
9302                              lastLoadGameTitle, lastLoadGameUseList);
9303     } else {
9304         return LoadGame(lastLoadGameFP, gameNumber,
9305                         lastLoadGameTitle, lastLoadGameUseList);
9306     }
9307 }
9308
9309
9310
9311 /* Load the nth game from open file f */
9312 int
9313 LoadGame(f, gameNumber, title, useList)
9314      FILE *f;
9315      int gameNumber;
9316      char *title;
9317      int useList;
9318 {
9319     ChessMove cm;
9320     char buf[MSG_SIZ];
9321     int gn = gameNumber;
9322     ListGame *lg = NULL;
9323     int numPGNTags = 0;
9324     int err;
9325     GameMode oldGameMode;
9326     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9327
9328     if (appData.debugMode)
9329         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9330
9331     if (gameMode == Training )
9332         SetTrainingModeOff();
9333
9334     oldGameMode = gameMode;
9335     if (gameMode != BeginningOfGame) 
9336       {
9337         Reset(FALSE, TRUE);
9338       };
9339
9340     gameFileFP = f;
9341     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9342       {
9343         fclose(lastLoadGameFP);
9344       };
9345
9346     if (useList) 
9347       {
9348         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9349         
9350         if (lg) 
9351           {
9352             fseek(f, lg->offset, 0);
9353             GameListHighlight(gameNumber);
9354             gn = 1;
9355           }
9356         else 
9357           {
9358             DisplayError(_("Game number out of range"), 0);
9359             return FALSE;
9360           };
9361       } 
9362     else 
9363       {
9364         GameListDestroy();
9365         if (fseek(f, 0, 0) == -1) 
9366           {
9367             if (f == lastLoadGameFP ?
9368                 gameNumber == lastLoadGameNumber + 1 :
9369                 gameNumber == 1) 
9370               {
9371                 gn = 1;
9372               } 
9373             else 
9374               {
9375                 DisplayError(_("Can't seek on game file"), 0);
9376                 return FALSE;
9377               };
9378           };
9379       };
9380
9381     lastLoadGameFP      = f;
9382     lastLoadGameNumber  = gameNumber;
9383     strcpy(lastLoadGameTitle, title);
9384     lastLoadGameUseList = useList;
9385
9386     yynewfile(f);
9387
9388     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9389       {
9390         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9391                  lg->gameInfo.black);
9392         DisplayTitle(buf);
9393       } 
9394     else if (*title != NULLCHAR) 
9395       {
9396         if (gameNumber > 1) 
9397           {
9398             sprintf(buf, "%s %d", title, gameNumber);
9399             DisplayTitle(buf);
9400           } 
9401         else 
9402           {
9403             DisplayTitle(title);
9404           };
9405       };
9406
9407     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9408       {
9409         gameMode = PlayFromGameFile;
9410         ModeHighlight();
9411       };
9412
9413     currentMove = forwardMostMove = backwardMostMove = 0;
9414     CopyBoard(boards[0], initialPosition);
9415     StopClocks();
9416
9417     /*
9418      * Skip the first gn-1 games in the file.
9419      * Also skip over anything that precedes an identifiable
9420      * start of game marker, to avoid being confused by
9421      * garbage at the start of the file.  Currently
9422      * recognized start of game markers are the move number "1",
9423      * the pattern "gnuchess .* game", the pattern
9424      * "^[#;%] [^ ]* game file", and a PGN tag block.
9425      * A game that starts with one of the latter two patterns
9426      * will also have a move number 1, possibly
9427      * following a position diagram.
9428      * 5-4-02: Let's try being more lenient and allowing a game to
9429      * start with an unnumbered move.  Does that break anything?
9430      */
9431     cm = lastLoadGameStart = (ChessMove) 0;
9432     while (gn > 0) {
9433         yyboardindex = forwardMostMove;
9434         cm = (ChessMove) yylex();
9435         switch (cm) {
9436           case (ChessMove) 0:
9437             if (cmailMsgLoaded) {
9438                 nCmailGames = CMAIL_MAX_GAMES - gn;
9439             } else {
9440                 Reset(TRUE, TRUE);
9441                 DisplayError(_("Game not found in file"), 0);
9442             }
9443             return FALSE;
9444
9445           case GNUChessGame:
9446           case XBoardGame:
9447             gn--;
9448             lastLoadGameStart = cm;
9449             break;
9450
9451           case MoveNumberOne:
9452             switch (lastLoadGameStart) {
9453               case GNUChessGame:
9454               case XBoardGame:
9455               case PGNTag:
9456                 break;
9457               case MoveNumberOne:
9458               case (ChessMove) 0:
9459                 gn--;           /* count this game */
9460                 lastLoadGameStart = cm;
9461                 break;
9462               default:
9463                 /* impossible */
9464                 break;
9465             }
9466             break;
9467
9468           case PGNTag:
9469             switch (lastLoadGameStart) {
9470               case GNUChessGame:
9471               case PGNTag:
9472               case MoveNumberOne:
9473               case (ChessMove) 0:
9474                 gn--;           /* count this game */
9475                 lastLoadGameStart = cm;
9476                 break;
9477               case XBoardGame:
9478                 lastLoadGameStart = cm; /* game counted already */
9479                 break;
9480               default:
9481                 /* impossible */
9482                 break;
9483             }
9484             if (gn > 0) {
9485                 do {
9486                     yyboardindex = forwardMostMove;
9487                     cm = (ChessMove) yylex();
9488                 } while (cm == PGNTag || cm == Comment);
9489             }
9490             break;
9491
9492           case WhiteWins:
9493           case BlackWins:
9494           case GameIsDrawn:
9495             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9496                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9497                     != CMAIL_OLD_RESULT) {
9498                     nCmailResults ++ ;
9499                     cmailResult[  CMAIL_MAX_GAMES
9500                                 - gn - 1] = CMAIL_OLD_RESULT;
9501                 }
9502             }
9503             break;
9504
9505           case NormalMove:
9506             /* Only a NormalMove can be at the start of a game
9507              * without a position diagram. */
9508             if (lastLoadGameStart == (ChessMove) 0) {
9509               gn--;
9510               lastLoadGameStart = MoveNumberOne;
9511             }
9512             break;
9513
9514           default:
9515             break;
9516         }
9517     }
9518
9519     if (appData.debugMode)
9520       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9521
9522     if (cm == XBoardGame) {
9523         /* Skip any header junk before position diagram and/or move 1 */
9524         for (;;) {
9525             yyboardindex = forwardMostMove;
9526             cm = (ChessMove) yylex();
9527
9528             if (cm == (ChessMove) 0 ||
9529                 cm == GNUChessGame || cm == XBoardGame) {
9530                 /* Empty game; pretend end-of-file and handle later */
9531                 cm = (ChessMove) 0;
9532                 break;
9533             }
9534
9535             if (cm == MoveNumberOne || cm == PositionDiagram ||
9536                 cm == PGNTag || cm == Comment)
9537               break;
9538         }
9539     } else if (cm == GNUChessGame) {
9540         if (gameInfo.event != NULL) {
9541             free(gameInfo.event);
9542         }
9543         gameInfo.event = StrSave(yy_text);
9544     }
9545
9546     startedFromSetupPosition = FALSE;
9547     while (cm == PGNTag) {
9548         if (appData.debugMode)
9549           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9550         err = ParsePGNTag(yy_text, &gameInfo);
9551         if (!err) numPGNTags++;
9552
9553         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9554         if(gameInfo.variant != oldVariant) {
9555             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9556             InitPosition(TRUE);
9557             oldVariant = gameInfo.variant;
9558             if (appData.debugMode)
9559               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9560         }
9561
9562
9563         if (gameInfo.fen != NULL) {
9564           Board initial_position;
9565           startedFromSetupPosition = TRUE;
9566           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9567             Reset(TRUE, TRUE);
9568             DisplayError(_("Bad FEN position in file"), 0);
9569             return FALSE;
9570           }
9571           CopyBoard(boards[0], initial_position);
9572           if (blackPlaysFirst) {
9573             currentMove = forwardMostMove = backwardMostMove = 1;
9574             CopyBoard(boards[1], initial_position);
9575             strcpy(moveList[0], "");
9576             strcpy(parseList[0], "");
9577             timeRemaining[0][1] = whiteTimeRemaining;
9578             timeRemaining[1][1] = blackTimeRemaining;
9579             if (commentList[0] != NULL) {
9580               commentList[1] = commentList[0];
9581               commentList[0] = NULL;
9582             }
9583           } else {
9584             currentMove = forwardMostMove = backwardMostMove = 0;
9585           }
9586           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9587           {   int i;
9588               initialRulePlies = FENrulePlies;
9589               for( i=0; i< nrCastlingRights; i++ )
9590                   initialRights[i] = initial_position[CASTLING][i];
9591           }
9592           yyboardindex = forwardMostMove;
9593           free(gameInfo.fen);
9594           gameInfo.fen = NULL;
9595         }
9596
9597         yyboardindex = forwardMostMove;
9598         cm = (ChessMove) yylex();
9599
9600         /* Handle comments interspersed among the tags */
9601         while (cm == Comment) {
9602             char *p;
9603             if (appData.debugMode)
9604               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9605             p = yy_text;
9606             AppendComment(currentMove, p, FALSE);
9607             yyboardindex = forwardMostMove;
9608             cm = (ChessMove) yylex();
9609         }
9610     }
9611
9612     /* don't rely on existence of Event tag since if game was
9613      * pasted from clipboard the Event tag may not exist
9614      */
9615     if (numPGNTags > 0){
9616         char *tags;
9617         if (gameInfo.variant == VariantNormal) {
9618           gameInfo.variant = StringToVariant(gameInfo.event);
9619         }
9620         if (!matchMode) {
9621           if( appData.autoDisplayTags ) {
9622             tags = PGNTags(&gameInfo);
9623             TagsPopUp(tags, CmailMsg());
9624             free(tags);
9625           }
9626         }
9627     } else {
9628         /* Make something up, but don't display it now */
9629         SetGameInfo();
9630         TagsPopDown();
9631     }
9632
9633     if (cm == PositionDiagram) {
9634         int i, j;
9635         char *p;
9636         Board initial_position;
9637
9638         if (appData.debugMode)
9639           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9640
9641         if (!startedFromSetupPosition) {
9642             p = yy_text;
9643             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9644               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9645                 switch (*p) {
9646                   case '[':
9647                   case '-':
9648                   case ' ':
9649                   case '\t':
9650                   case '\n':
9651                   case '\r':
9652                     break;
9653                   default:
9654                     initial_position[i][j++] = CharToPiece(*p);
9655                     break;
9656                 }
9657             while (*p == ' ' || *p == '\t' ||
9658                    *p == '\n' || *p == '\r') p++;
9659
9660             if (strncmp(p, "black", strlen("black"))==0)
9661               blackPlaysFirst = TRUE;
9662             else
9663               blackPlaysFirst = FALSE;
9664             startedFromSetupPosition = TRUE;
9665
9666             CopyBoard(boards[0], initial_position);
9667             if (blackPlaysFirst) {
9668                 currentMove = forwardMostMove = backwardMostMove = 1;
9669                 CopyBoard(boards[1], initial_position);
9670                 strcpy(moveList[0], "");
9671                 strcpy(parseList[0], "");
9672                 timeRemaining[0][1] = whiteTimeRemaining;
9673                 timeRemaining[1][1] = blackTimeRemaining;
9674                 if (commentList[0] != NULL) {
9675                     commentList[1] = commentList[0];
9676                     commentList[0] = NULL;
9677                 }
9678             } else {
9679                 currentMove = forwardMostMove = backwardMostMove = 0;
9680             }
9681         }
9682         yyboardindex = forwardMostMove;
9683         cm = (ChessMove) yylex();
9684     }
9685
9686     if (first.pr == NoProc) {
9687         StartChessProgram(&first);
9688     }
9689     InitChessProgram(&first, FALSE);
9690     SendToProgram("force\n", &first);
9691     if (startedFromSetupPosition) {
9692         SendBoard(&first, forwardMostMove);
9693     if (appData.debugMode) {
9694         fprintf(debugFP, "Load Game\n");
9695     }
9696         DisplayBothClocks();
9697     }
9698
9699     /* [HGM] server: flag to write setup moves in broadcast file as one */
9700     loadFlag = appData.suppressLoadMoves;
9701
9702     while (cm == Comment) {
9703         char *p;
9704         if (appData.debugMode)
9705           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9706         p = yy_text;
9707         AppendComment(currentMove, p, FALSE);
9708         yyboardindex = forwardMostMove;
9709         cm = (ChessMove) yylex();
9710     }
9711
9712     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9713         cm == WhiteWins || cm == BlackWins ||
9714         cm == GameIsDrawn || cm == GameUnfinished) {
9715         DisplayMessage("", _("No moves in game"));
9716         if (cmailMsgLoaded) {
9717             if (appData.debugMode)
9718               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9719             ClearHighlights();
9720             flipView = FALSE;
9721         }
9722         DrawPosition(FALSE, boards[currentMove]);
9723         DisplayBothClocks();
9724         gameMode = EditGame;
9725         ModeHighlight();
9726         gameFileFP = NULL;
9727         cmailOldMove = 0;
9728         return TRUE;
9729     }
9730
9731     // [HGM] PV info: routine tests if comment empty
9732     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9733         DisplayComment(currentMove - 1, commentList[currentMove]);
9734     }
9735     if (!matchMode && appData.timeDelay != 0)
9736       DrawPosition(FALSE, boards[currentMove]);
9737
9738     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9739       programStats.ok_to_send = 1;
9740     }
9741
9742     /* if the first token after the PGN tags is a move
9743      * and not move number 1, retrieve it from the parser
9744      */
9745     if (cm != MoveNumberOne)
9746         LoadGameOneMove(cm);
9747
9748     /* load the remaining moves from the file */
9749     while (LoadGameOneMove((ChessMove)0)) {
9750       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9751       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9752     }
9753
9754     /* rewind to the start of the game */
9755     currentMove = backwardMostMove;
9756
9757     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9758
9759     if (oldGameMode == AnalyzeFile ||
9760         oldGameMode == AnalyzeMode) {
9761       AnalyzeFileEvent();
9762     }
9763
9764     if (matchMode || appData.timeDelay == 0) {
9765       ToEndEvent();
9766       gameMode = EditGame;
9767       ModeHighlight();
9768     } else if (appData.timeDelay > 0) {
9769       AutoPlayGameLoop();
9770     }
9771
9772     if (appData.debugMode)
9773         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9774
9775     loadFlag = 0; /* [HGM] true game starts */
9776     return TRUE;
9777 }
9778
9779 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9780 int
9781 ReloadPosition(offset)
9782      int offset;
9783 {
9784     int positionNumber = lastLoadPositionNumber + offset;
9785     if (lastLoadPositionFP == NULL) {
9786         DisplayError(_("No position has been loaded yet"), 0);
9787         return FALSE;
9788     }
9789     if (positionNumber <= 0) {
9790         DisplayError(_("Can't back up any further"), 0);
9791         return FALSE;
9792     }
9793     return LoadPosition(lastLoadPositionFP, positionNumber,
9794                         lastLoadPositionTitle);
9795 }
9796
9797 /* Load the nth position from the given file */
9798 int
9799 LoadPositionFromFile(filename, n, title)
9800      char *filename;
9801      int n;
9802      char *title;
9803 {
9804     FILE *f;
9805     char buf[MSG_SIZ];
9806
9807     if (strcmp(filename, "-") == 0) {
9808         return LoadPosition(stdin, n, "stdin");
9809     } else {
9810         f = fopen(filename, "rb");
9811         if (f == NULL) {
9812             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9813             DisplayError(buf, errno);
9814             return FALSE;
9815         } else {
9816             return LoadPosition(f, n, title);
9817         }
9818     }
9819 }
9820
9821 /* Load the nth position from the given open file, and close it */
9822 int
9823 LoadPosition(f, positionNumber, title)
9824      FILE *f;
9825      int positionNumber;
9826      char *title;
9827 {
9828     char *p, line[MSG_SIZ];
9829     Board initial_position;
9830     int i, j, fenMode, pn;
9831
9832     if (gameMode == Training )
9833         SetTrainingModeOff();
9834
9835     if (gameMode != BeginningOfGame) {
9836         Reset(FALSE, TRUE);
9837     }
9838     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9839         fclose(lastLoadPositionFP);
9840     }
9841     if (positionNumber == 0) positionNumber = 1;
9842     lastLoadPositionFP = f;
9843     lastLoadPositionNumber = positionNumber;
9844     strcpy(lastLoadPositionTitle, title);
9845     if (first.pr == NoProc) {
9846       StartChessProgram(&first);
9847       InitChessProgram(&first, FALSE);
9848     }
9849     pn = positionNumber;
9850     if (positionNumber < 0) {
9851         /* Negative position number means to seek to that byte offset */
9852         if (fseek(f, -positionNumber, 0) == -1) {
9853             DisplayError(_("Can't seek on position file"), 0);
9854             return FALSE;
9855         };
9856         pn = 1;
9857     } else {
9858         if (fseek(f, 0, 0) == -1) {
9859             if (f == lastLoadPositionFP ?
9860                 positionNumber == lastLoadPositionNumber + 1 :
9861                 positionNumber == 1) {
9862                 pn = 1;
9863             } else {
9864                 DisplayError(_("Can't seek on position file"), 0);
9865                 return FALSE;
9866             }
9867         }
9868     }
9869     /* See if this file is FEN or old-style xboard */
9870     if (fgets(line, MSG_SIZ, f) == NULL) {
9871         DisplayError(_("Position not found in file"), 0);
9872         return FALSE;
9873     }
9874     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9875     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9876
9877     if (pn >= 2) {
9878         if (fenMode || line[0] == '#') pn--;
9879         while (pn > 0) {
9880             /* skip positions before number pn */
9881             if (fgets(line, MSG_SIZ, f) == NULL) {
9882                 Reset(TRUE, TRUE);
9883                 DisplayError(_("Position not found in file"), 0);
9884                 return FALSE;
9885             }
9886             if (fenMode || line[0] == '#') pn--;
9887         }
9888     }
9889
9890     if (fenMode) {
9891         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9892             DisplayError(_("Bad FEN position in file"), 0);
9893             return FALSE;
9894         }
9895     } else {
9896         (void) fgets(line, MSG_SIZ, f);
9897         (void) fgets(line, MSG_SIZ, f);
9898
9899         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9900             (void) fgets(line, MSG_SIZ, f);
9901             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9902                 if (*p == ' ')
9903                   continue;
9904                 initial_position[i][j++] = CharToPiece(*p);
9905             }
9906         }
9907
9908         blackPlaysFirst = FALSE;
9909         if (!feof(f)) {
9910             (void) fgets(line, MSG_SIZ, f);
9911             if (strncmp(line, "black", strlen("black"))==0)
9912               blackPlaysFirst = TRUE;
9913         }
9914     }
9915     startedFromSetupPosition = TRUE;
9916
9917     SendToProgram("force\n", &first);
9918     CopyBoard(boards[0], initial_position);
9919     if (blackPlaysFirst) {
9920         currentMove = forwardMostMove = backwardMostMove = 1;
9921         strcpy(moveList[0], "");
9922         strcpy(parseList[0], "");
9923         CopyBoard(boards[1], initial_position);
9924         DisplayMessage("", _("Black to play"));
9925     } else {
9926         currentMove = forwardMostMove = backwardMostMove = 0;
9927         DisplayMessage("", _("White to play"));
9928     }
9929     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9930     SendBoard(&first, forwardMostMove);
9931     if (appData.debugMode) {
9932 int i, j;
9933   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9934   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9935         fprintf(debugFP, "Load Position\n");
9936     }
9937
9938     if (positionNumber > 1) {
9939         sprintf(line, "%s %d", title, positionNumber);
9940         DisplayTitle(line);
9941     } else {
9942         DisplayTitle(title);
9943     }
9944     gameMode = EditGame;
9945     ModeHighlight();
9946     ResetClocks();
9947     timeRemaining[0][1] = whiteTimeRemaining;
9948     timeRemaining[1][1] = blackTimeRemaining;
9949     DrawPosition(FALSE, boards[currentMove]);
9950
9951     return TRUE;
9952 }
9953
9954
9955 void
9956 CopyPlayerNameIntoFileName(dest, src)
9957      char **dest, *src;
9958 {
9959     while (*src != NULLCHAR && *src != ',') {
9960         if (*src == ' ') {
9961             *(*dest)++ = '_';
9962             src++;
9963         } else {
9964             *(*dest)++ = *src++;
9965         }
9966     }
9967 }
9968
9969 char *DefaultFileName(ext)
9970      char *ext;
9971 {
9972     static char def[MSG_SIZ];
9973     char *p;
9974
9975     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9976         p = def;
9977         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9978         *p++ = '-';
9979         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9980         *p++ = '.';
9981         strcpy(p, ext);
9982     } else {
9983         def[0] = NULLCHAR;
9984     }
9985     return def;
9986 }
9987
9988 /* Save the current game to the given file */
9989 int
9990 SaveGameToFile(filename, append)
9991      char *filename;
9992      int append;
9993 {
9994     FILE *f;
9995     char buf[MSG_SIZ];
9996
9997     if (strcmp(filename, "-") == 0) {
9998         return SaveGame(stdout, 0, NULL);
9999     } else {
10000         f = fopen(filename, append ? "a" : "w");
10001         if (f == NULL) {
10002             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10003             DisplayError(buf, errno);
10004             return FALSE;
10005         } else {
10006             return SaveGame(f, 0, NULL);
10007         }
10008     }
10009 }
10010
10011 char *
10012 SavePart(str)
10013      char *str;
10014 {
10015     static char buf[MSG_SIZ];
10016     char *p;
10017
10018     p = strchr(str, ' ');
10019     if (p == NULL) return str;
10020     strncpy(buf, str, p - str);
10021     buf[p - str] = NULLCHAR;
10022     return buf;
10023 }
10024
10025 #define PGN_MAX_LINE 75
10026
10027 #define PGN_SIDE_WHITE  0
10028 #define PGN_SIDE_BLACK  1
10029
10030 /* [AS] */
10031 static int FindFirstMoveOutOfBook( int side )
10032 {
10033     int result = -1;
10034
10035     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10036         int index = backwardMostMove;
10037         int has_book_hit = 0;
10038
10039         if( (index % 2) != side ) {
10040             index++;
10041         }
10042
10043         while( index < forwardMostMove ) {
10044             /* Check to see if engine is in book */
10045             int depth = pvInfoList[index].depth;
10046             int score = pvInfoList[index].score;
10047             int in_book = 0;
10048
10049             if( depth <= 2 ) {
10050                 in_book = 1;
10051             }
10052             else if( score == 0 && depth == 63 ) {
10053                 in_book = 1; /* Zappa */
10054             }
10055             else if( score == 2 && depth == 99 ) {
10056                 in_book = 1; /* Abrok */
10057             }
10058
10059             has_book_hit += in_book;
10060
10061             if( ! in_book ) {
10062                 result = index;
10063
10064                 break;
10065             }
10066
10067             index += 2;
10068         }
10069     }
10070
10071     return result;
10072 }
10073
10074 /* [AS] */
10075 void GetOutOfBookInfo( char * buf )
10076 {
10077     int oob[2];
10078     int i;
10079     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10080
10081     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10082     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10083
10084     *buf = '\0';
10085
10086     if( oob[0] >= 0 || oob[1] >= 0 ) {
10087         for( i=0; i<2; i++ ) {
10088             int idx = oob[i];
10089
10090             if( idx >= 0 ) {
10091                 if( i > 0 && oob[0] >= 0 ) {
10092                     strcat( buf, "   " );
10093                 }
10094
10095                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10096                 sprintf( buf+strlen(buf), "%s%.2f",
10097                     pvInfoList[idx].score >= 0 ? "+" : "",
10098                     pvInfoList[idx].score / 100.0 );
10099             }
10100         }
10101     }
10102 }
10103
10104 /* Save game in PGN style and close the file */
10105 int
10106 SaveGamePGN(f)
10107      FILE *f;
10108 {
10109     int i, offset, linelen, newblock;
10110     time_t tm;
10111 //    char *movetext;
10112     char numtext[32];
10113     int movelen, numlen, blank;
10114     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10115
10116     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10117
10118     tm = time((time_t *) NULL);
10119
10120     PrintPGNTags(f, &gameInfo);
10121
10122     if (backwardMostMove > 0 || startedFromSetupPosition) {
10123         char *fen = PositionToFEN(backwardMostMove, NULL);
10124         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10125         fprintf(f, "\n{--------------\n");
10126         PrintPosition(f, backwardMostMove);
10127         fprintf(f, "--------------}\n");
10128         free(fen);
10129     }
10130     else {
10131         /* [AS] Out of book annotation */
10132         if( appData.saveOutOfBookInfo ) {
10133             char buf[64];
10134
10135             GetOutOfBookInfo( buf );
10136
10137             if( buf[0] != '\0' ) {
10138                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10139             }
10140         }
10141
10142         fprintf(f, "\n");
10143     }
10144
10145     i = backwardMostMove;
10146     linelen = 0;
10147     newblock = TRUE;
10148
10149     while (i < forwardMostMove) {
10150         /* Print comments preceding this move */
10151         if (commentList[i] != NULL) {
10152             if (linelen > 0) fprintf(f, "\n");
10153             fprintf(f, "%s", commentList[i]);
10154             linelen = 0;
10155             newblock = TRUE;
10156         }
10157
10158         /* Format move number */
10159         if ((i % 2) == 0) {
10160             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10161         } else {
10162             if (newblock) {
10163                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10164             } else {
10165                 numtext[0] = NULLCHAR;
10166             }
10167         }
10168         numlen = strlen(numtext);
10169         newblock = FALSE;
10170
10171         /* Print move number */
10172         blank = linelen > 0 && numlen > 0;
10173         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10174             fprintf(f, "\n");
10175             linelen = 0;
10176             blank = 0;
10177         }
10178         if (blank) {
10179             fprintf(f, " ");
10180             linelen++;
10181         }
10182         fprintf(f, "%s", numtext);
10183         linelen += numlen;
10184
10185         /* Get move */
10186         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10187         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10188
10189         /* Print move */
10190         blank = linelen > 0 && movelen > 0;
10191         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10192             fprintf(f, "\n");
10193             linelen = 0;
10194             blank = 0;
10195         }
10196         if (blank) {
10197             fprintf(f, " ");
10198             linelen++;
10199         }
10200         fprintf(f, "%s", move_buffer);
10201         linelen += movelen;
10202
10203         /* [AS] Add PV info if present */
10204         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10205             /* [HGM] add time */
10206             char buf[MSG_SIZ]; int seconds;
10207
10208             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10209
10210             if( seconds <= 0) buf[0] = 0; else
10211             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10212                 seconds = (seconds + 4)/10; // round to full seconds
10213                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10214                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10215             }
10216
10217             sprintf( move_buffer, "{%s%.2f/%d%s}",
10218                 pvInfoList[i].score >= 0 ? "+" : "",
10219                 pvInfoList[i].score / 100.0,
10220                 pvInfoList[i].depth,
10221                 buf );
10222
10223             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10224
10225             /* Print score/depth */
10226             blank = linelen > 0 && movelen > 0;
10227             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10228                 fprintf(f, "\n");
10229                 linelen = 0;
10230                 blank = 0;
10231             }
10232             if (blank) {
10233                 fprintf(f, " ");
10234                 linelen++;
10235             }
10236             fprintf(f, "%s", move_buffer);
10237             linelen += movelen;
10238         }
10239
10240         i++;
10241     }
10242
10243     /* Start a new line */
10244     if (linelen > 0) fprintf(f, "\n");
10245
10246     /* Print comments after last move */
10247     if (commentList[i] != NULL) {
10248         fprintf(f, "%s\n", commentList[i]);
10249     }
10250
10251     /* Print result */
10252     if (gameInfo.resultDetails != NULL &&
10253         gameInfo.resultDetails[0] != NULLCHAR) {
10254         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10255                 PGNResult(gameInfo.result));
10256     } else {
10257         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10258     }
10259
10260     fclose(f);
10261     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10262     return TRUE;
10263 }
10264
10265 /* Save game in old style and close the file */
10266 int
10267 SaveGameOldStyle(f)
10268      FILE *f;
10269 {
10270     int i, offset;
10271     time_t tm;
10272
10273     tm = time((time_t *) NULL);
10274
10275     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10276     PrintOpponents(f);
10277
10278     if (backwardMostMove > 0 || startedFromSetupPosition) {
10279         fprintf(f, "\n[--------------\n");
10280         PrintPosition(f, backwardMostMove);
10281         fprintf(f, "--------------]\n");
10282     } else {
10283         fprintf(f, "\n");
10284     }
10285
10286     i = backwardMostMove;
10287     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10288
10289     while (i < forwardMostMove) {
10290         if (commentList[i] != NULL) {
10291             fprintf(f, "[%s]\n", commentList[i]);
10292         }
10293
10294         if ((i % 2) == 1) {
10295             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10296             i++;
10297         } else {
10298             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10299             i++;
10300             if (commentList[i] != NULL) {
10301                 fprintf(f, "\n");
10302                 continue;
10303             }
10304             if (i >= forwardMostMove) {
10305                 fprintf(f, "\n");
10306                 break;
10307             }
10308             fprintf(f, "%s\n", parseList[i]);
10309             i++;
10310         }
10311     }
10312
10313     if (commentList[i] != NULL) {
10314         fprintf(f, "[%s]\n", commentList[i]);
10315     }
10316
10317     /* This isn't really the old style, but it's close enough */
10318     if (gameInfo.resultDetails != NULL &&
10319         gameInfo.resultDetails[0] != NULLCHAR) {
10320         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10321                 gameInfo.resultDetails);
10322     } else {
10323         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10324     }
10325
10326     fclose(f);
10327     return TRUE;
10328 }
10329
10330 /* Save the current game to open file f and close the file */
10331 int
10332 SaveGame(f, dummy, dummy2)
10333      FILE *f;
10334      int dummy;
10335      char *dummy2;
10336 {
10337     if (gameMode == EditPosition) EditPositionDone(TRUE);
10338     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10339     if (appData.oldSaveStyle)
10340       return SaveGameOldStyle(f);
10341     else
10342       return SaveGamePGN(f);
10343 }
10344
10345 /* Save the current position to the given file */
10346 int
10347 SavePositionToFile(filename)
10348      char *filename;
10349 {
10350     FILE *f;
10351     char buf[MSG_SIZ];
10352
10353     if (strcmp(filename, "-") == 0) {
10354         return SavePosition(stdout, 0, NULL);
10355     } else {
10356         f = fopen(filename, "a");
10357         if (f == NULL) {
10358             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10359             DisplayError(buf, errno);
10360             return FALSE;
10361         } else {
10362             SavePosition(f, 0, NULL);
10363             return TRUE;
10364         }
10365     }
10366 }
10367
10368 /* Save the current position to the given open file and close the file */
10369 int
10370 SavePosition(f, dummy, dummy2)
10371      FILE *f;
10372      int dummy;
10373      char *dummy2;
10374 {
10375     time_t tm;
10376     char *fen;
10377     if (gameMode == EditPosition) EditPositionDone(TRUE);
10378     if (appData.oldSaveStyle) {
10379         tm = time((time_t *) NULL);
10380
10381         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10382         PrintOpponents(f);
10383         fprintf(f, "[--------------\n");
10384         PrintPosition(f, currentMove);
10385         fprintf(f, "--------------]\n");
10386     } else {
10387         fen = PositionToFEN(currentMove, NULL);
10388         fprintf(f, "%s\n", fen);
10389         free(fen);
10390     }
10391     fclose(f);
10392     return TRUE;
10393 }
10394
10395 void
10396 ReloadCmailMsgEvent(unregister)
10397      int unregister;
10398 {
10399 #if !WIN32
10400     static char *inFilename = NULL;
10401     static char *outFilename;
10402     int i;
10403     struct stat inbuf, outbuf;
10404     int status;
10405
10406     /* Any registered moves are unregistered if unregister is set, */
10407     /* i.e. invoked by the signal handler */
10408     if (unregister) {
10409         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10410             cmailMoveRegistered[i] = FALSE;
10411             if (cmailCommentList[i] != NULL) {
10412                 free(cmailCommentList[i]);
10413                 cmailCommentList[i] = NULL;
10414             }
10415         }
10416         nCmailMovesRegistered = 0;
10417     }
10418
10419     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10420         cmailResult[i] = CMAIL_NOT_RESULT;
10421     }
10422     nCmailResults = 0;
10423
10424     if (inFilename == NULL) {
10425         /* Because the filenames are static they only get malloced once  */
10426         /* and they never get freed                                      */
10427         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10428         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10429
10430         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10431         sprintf(outFilename, "%s.out", appData.cmailGameName);
10432     }
10433
10434     status = stat(outFilename, &outbuf);
10435     if (status < 0) {
10436         cmailMailedMove = FALSE;
10437     } else {
10438         status = stat(inFilename, &inbuf);
10439         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10440     }
10441
10442     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10443        counts the games, notes how each one terminated, etc.
10444
10445        It would be nice to remove this kludge and instead gather all
10446        the information while building the game list.  (And to keep it
10447        in the game list nodes instead of having a bunch of fixed-size
10448        parallel arrays.)  Note this will require getting each game's
10449        termination from the PGN tags, as the game list builder does
10450        not process the game moves.  --mann
10451        */
10452     cmailMsgLoaded = TRUE;
10453     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10454
10455     /* Load first game in the file or popup game menu */
10456     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10457
10458 #endif /* !WIN32 */
10459     return;
10460 }
10461
10462 int
10463 RegisterMove()
10464 {
10465     FILE *f;
10466     char string[MSG_SIZ];
10467
10468     if (   cmailMailedMove
10469         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10470         return TRUE;            /* Allow free viewing  */
10471     }
10472
10473     /* Unregister move to ensure that we don't leave RegisterMove        */
10474     /* with the move registered when the conditions for registering no   */
10475     /* longer hold                                                       */
10476     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10477         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10478         nCmailMovesRegistered --;
10479
10480         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10481           {
10482               free(cmailCommentList[lastLoadGameNumber - 1]);
10483               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10484           }
10485     }
10486
10487     if (cmailOldMove == -1) {
10488         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10489         return FALSE;
10490     }
10491
10492     if (currentMove > cmailOldMove + 1) {
10493         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10494         return FALSE;
10495     }
10496
10497     if (currentMove < cmailOldMove) {
10498         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10499         return FALSE;
10500     }
10501
10502     if (forwardMostMove > currentMove) {
10503         /* Silently truncate extra moves */
10504         TruncateGame();
10505     }
10506
10507     if (   (currentMove == cmailOldMove + 1)
10508         || (   (currentMove == cmailOldMove)
10509             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10510                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10511         if (gameInfo.result != GameUnfinished) {
10512             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10513         }
10514
10515         if (commentList[currentMove] != NULL) {
10516             cmailCommentList[lastLoadGameNumber - 1]
10517               = StrSave(commentList[currentMove]);
10518         }
10519         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10520
10521         if (appData.debugMode)
10522           fprintf(debugFP, "Saving %s for game %d\n",
10523                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10524
10525         sprintf(string,
10526                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10527
10528         f = fopen(string, "w");
10529         if (appData.oldSaveStyle) {
10530             SaveGameOldStyle(f); /* also closes the file */
10531
10532             sprintf(string, "%s.pos.out", appData.cmailGameName);
10533             f = fopen(string, "w");
10534             SavePosition(f, 0, NULL); /* also closes the file */
10535         } else {
10536             fprintf(f, "{--------------\n");
10537             PrintPosition(f, currentMove);
10538             fprintf(f, "--------------}\n\n");
10539
10540             SaveGame(f, 0, NULL); /* also closes the file*/
10541         }
10542
10543         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10544         nCmailMovesRegistered ++;
10545     } else if (nCmailGames == 1) {
10546         DisplayError(_("You have not made a move yet"), 0);
10547         return FALSE;
10548     }
10549
10550     return TRUE;
10551 }
10552
10553 void
10554 MailMoveEvent()
10555 {
10556 #if !WIN32
10557     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10558     FILE *commandOutput;
10559     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10560     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10561     int nBuffers;
10562     int i;
10563     int archived;
10564     char *arcDir;
10565
10566     if (! cmailMsgLoaded) {
10567         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10568         return;
10569     }
10570
10571     if (nCmailGames == nCmailResults) {
10572         DisplayError(_("No unfinished games"), 0);
10573         return;
10574     }
10575
10576 #if CMAIL_PROHIBIT_REMAIL
10577     if (cmailMailedMove) {
10578         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);
10579         DisplayError(msg, 0);
10580         return;
10581     }
10582 #endif
10583
10584     if (! (cmailMailedMove || RegisterMove())) return;
10585
10586     if (   cmailMailedMove
10587         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10588         sprintf(string, partCommandString,
10589                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10590         commandOutput = popen(string, "r");
10591
10592         if (commandOutput == NULL) {
10593             DisplayError(_("Failed to invoke cmail"), 0);
10594         } else {
10595             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10596                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10597             }
10598             if (nBuffers > 1) {
10599                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10600                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10601                 nBytes = MSG_SIZ - 1;
10602             } else {
10603                 (void) memcpy(msg, buffer, nBytes);
10604             }
10605             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10606
10607             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10608                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10609
10610                 archived = TRUE;
10611                 for (i = 0; i < nCmailGames; i ++) {
10612                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10613                         archived = FALSE;
10614                     }
10615                 }
10616                 if (   archived
10617                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10618                         != NULL)) {
10619                     sprintf(buffer, "%s/%s.%s.archive",
10620                             arcDir,
10621                             appData.cmailGameName,
10622                             gameInfo.date);
10623                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10624                     cmailMsgLoaded = FALSE;
10625                 }
10626             }
10627
10628             DisplayInformation(msg);
10629             pclose(commandOutput);
10630         }
10631     } else {
10632         if ((*cmailMsg) != '\0') {
10633             DisplayInformation(cmailMsg);
10634         }
10635     }
10636
10637     return;
10638 #endif /* !WIN32 */
10639 }
10640
10641 char *
10642 CmailMsg()
10643 {
10644 #if WIN32
10645     return NULL;
10646 #else
10647     int  prependComma = 0;
10648     char number[5];
10649     char string[MSG_SIZ];       /* Space for game-list */
10650     int  i;
10651
10652     if (!cmailMsgLoaded) return "";
10653
10654     if (cmailMailedMove) {
10655         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10656     } else {
10657         /* Create a list of games left */
10658         sprintf(string, "[");
10659         for (i = 0; i < nCmailGames; i ++) {
10660             if (! (   cmailMoveRegistered[i]
10661                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10662                 if (prependComma) {
10663                     sprintf(number, ",%d", i + 1);
10664                 } else {
10665                     sprintf(number, "%d", i + 1);
10666                     prependComma = 1;
10667                 }
10668
10669                 strcat(string, number);
10670             }
10671         }
10672         strcat(string, "]");
10673
10674         if (nCmailMovesRegistered + nCmailResults == 0) {
10675             switch (nCmailGames) {
10676               case 1:
10677                 sprintf(cmailMsg,
10678                         _("Still need to make move for game\n"));
10679                 break;
10680
10681               case 2:
10682                 sprintf(cmailMsg,
10683                         _("Still need to make moves for both games\n"));
10684                 break;
10685
10686               default:
10687                 sprintf(cmailMsg,
10688                         _("Still need to make moves for all %d games\n"),
10689                         nCmailGames);
10690                 break;
10691             }
10692         } else {
10693             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10694               case 1:
10695                 sprintf(cmailMsg,
10696                         _("Still need to make a move for game %s\n"),
10697                         string);
10698                 break;
10699
10700               case 0:
10701                 if (nCmailResults == nCmailGames) {
10702                     sprintf(cmailMsg, _("No unfinished games\n"));
10703                 } else {
10704                     sprintf(cmailMsg, _("Ready to send mail\n"));
10705                 }
10706                 break;
10707
10708               default:
10709                 sprintf(cmailMsg,
10710                         _("Still need to make moves for games %s\n"),
10711                         string);
10712             }
10713         }
10714     }
10715     return cmailMsg;
10716 #endif /* WIN32 */
10717 }
10718
10719 void
10720 ResetGameEvent()
10721 {
10722     if (gameMode == Training)
10723       SetTrainingModeOff();
10724
10725     Reset(TRUE, TRUE);
10726     cmailMsgLoaded = FALSE;
10727     if (appData.icsActive) {
10728       SendToICS(ics_prefix);
10729       SendToICS("refresh\n");
10730     }
10731 }
10732
10733 void
10734 ExitEvent(status)
10735      int status;
10736 {
10737     exiting++;
10738     if (exiting > 2) {
10739       /* Give up on clean exit */
10740       exit(status);
10741     }
10742     if (exiting > 1) {
10743       /* Keep trying for clean exit */
10744       return;
10745     }
10746
10747     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10748
10749     if (telnetISR != NULL) {
10750       RemoveInputSource(telnetISR);
10751     }
10752     if (icsPR != NoProc) {
10753       DestroyChildProcess(icsPR, TRUE);
10754     }
10755
10756     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10757     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10758
10759     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10760     /* make sure this other one finishes before killing it!                  */
10761     if(endingGame) { int count = 0;
10762         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10763         while(endingGame && count++ < 10) DoSleep(1);
10764         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10765     }
10766
10767     /* Kill off chess programs */
10768     if (first.pr != NoProc) {
10769         ExitAnalyzeMode();
10770
10771         DoSleep( appData.delayBeforeQuit );
10772         SendToProgram("quit\n", &first);
10773         DoSleep( appData.delayAfterQuit );
10774         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10775     }
10776     if (second.pr != NoProc) {
10777         DoSleep( appData.delayBeforeQuit );
10778         SendToProgram("quit\n", &second);
10779         DoSleep( appData.delayAfterQuit );
10780         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10781     }
10782     if (first.isr != NULL) {
10783         RemoveInputSource(first.isr);
10784     }
10785     if (second.isr != NULL) {
10786         RemoveInputSource(second.isr);
10787     }
10788
10789     ShutDownFrontEnd();
10790     exit(status);
10791 }
10792
10793 void
10794 PauseEvent()
10795 {
10796     if (appData.debugMode)
10797         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10798     if (pausing) {
10799         pausing = FALSE;
10800         ModeHighlight();
10801         if (gameMode == MachinePlaysWhite ||
10802             gameMode == MachinePlaysBlack) {
10803             StartClocks();
10804         } else {
10805             DisplayBothClocks();
10806         }
10807         if (gameMode == PlayFromGameFile) {
10808             if (appData.timeDelay >= 0)
10809                 AutoPlayGameLoop();
10810         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10811             Reset(FALSE, TRUE);
10812             SendToICS(ics_prefix);
10813             SendToICS("refresh\n");
10814         } else if (currentMove < forwardMostMove) {
10815             ForwardInner(forwardMostMove);
10816         }
10817         pauseExamInvalid = FALSE;
10818     } else {
10819         switch (gameMode) {
10820           default:
10821             return;
10822           case IcsExamining:
10823             pauseExamForwardMostMove = forwardMostMove;
10824             pauseExamInvalid = FALSE;
10825             /* fall through */
10826           case IcsObserving:
10827           case IcsPlayingWhite:
10828           case IcsPlayingBlack:
10829             pausing = TRUE;
10830             ModeHighlight();
10831             return;
10832           case PlayFromGameFile:
10833             (void) StopLoadGameTimer();
10834             pausing = TRUE;
10835             ModeHighlight();
10836             break;
10837           case BeginningOfGame:
10838             if (appData.icsActive) return;
10839             /* else fall through */
10840           case MachinePlaysWhite:
10841           case MachinePlaysBlack:
10842           case TwoMachinesPlay:
10843             if (forwardMostMove == 0)
10844               return;           /* don't pause if no one has moved */
10845             if ((gameMode == MachinePlaysWhite &&
10846                  !WhiteOnMove(forwardMostMove)) ||
10847                 (gameMode == MachinePlaysBlack &&
10848                  WhiteOnMove(forwardMostMove))) {
10849                 StopClocks();
10850             }
10851             pausing = TRUE;
10852             ModeHighlight();
10853             break;
10854         }
10855     }
10856 }
10857
10858 void
10859 EditCommentEvent()
10860 {
10861     char title[MSG_SIZ];
10862
10863     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10864         strcpy(title, _("Edit comment"));
10865     } else {
10866         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10867                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10868                 parseList[currentMove - 1]);
10869     }
10870
10871     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10872 }
10873
10874
10875 void
10876 EditTagsEvent()
10877 {
10878     char *tags = PGNTags(&gameInfo);
10879     EditTagsPopUp(tags);
10880     free(tags);
10881 }
10882
10883 void
10884 AnalyzeModeEvent()
10885 {
10886     if (appData.noChessProgram || gameMode == AnalyzeMode)
10887       return;
10888
10889     if (gameMode != AnalyzeFile) {
10890         if (!appData.icsEngineAnalyze) {
10891                EditGameEvent();
10892                if (gameMode != EditGame) return;
10893         }
10894         ResurrectChessProgram();
10895         SendToProgram("analyze\n", &first);
10896         first.analyzing = TRUE;
10897         /*first.maybeThinking = TRUE;*/
10898         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10899         EngineOutputPopUp();
10900     }
10901     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10902     pausing = FALSE;
10903     ModeHighlight();
10904     SetGameInfo();
10905
10906     StartAnalysisClock();
10907     GetTimeMark(&lastNodeCountTime);
10908     lastNodeCount = 0;
10909 }
10910
10911 void
10912 AnalyzeFileEvent()
10913 {
10914     if (appData.noChessProgram || gameMode == AnalyzeFile)
10915       return;
10916
10917     if (gameMode != AnalyzeMode) {
10918         EditGameEvent();
10919         if (gameMode != EditGame) return;
10920         ResurrectChessProgram();
10921         SendToProgram("analyze\n", &first);
10922         first.analyzing = TRUE;
10923         /*first.maybeThinking = TRUE;*/
10924         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10925         EngineOutputPopUp();
10926     }
10927     gameMode = AnalyzeFile;
10928     pausing = FALSE;
10929     ModeHighlight();
10930     SetGameInfo();
10931
10932     StartAnalysisClock();
10933     GetTimeMark(&lastNodeCountTime);
10934     lastNodeCount = 0;
10935 }
10936
10937 void
10938 MachineWhiteEvent()
10939 {
10940     char buf[MSG_SIZ];
10941     char *bookHit = NULL;
10942
10943     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10944       return;
10945
10946
10947     if (gameMode == PlayFromGameFile ||
10948         gameMode == TwoMachinesPlay  ||
10949         gameMode == Training         ||
10950         gameMode == AnalyzeMode      ||
10951         gameMode == EndOfGame)
10952         EditGameEvent();
10953
10954     if (gameMode == EditPosition) 
10955         EditPositionDone(TRUE);
10956
10957     if (!WhiteOnMove(currentMove)) {
10958         DisplayError(_("It is not White's turn"), 0);
10959         return;
10960     }
10961
10962     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10963       ExitAnalyzeMode();
10964
10965     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10966         gameMode == AnalyzeFile)
10967         TruncateGame();
10968
10969     ResurrectChessProgram();    /* in case it isn't running */
10970     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10971         gameMode = MachinePlaysWhite;
10972         ResetClocks();
10973     } else
10974     gameMode = MachinePlaysWhite;
10975     pausing = FALSE;
10976     ModeHighlight();
10977     SetGameInfo();
10978     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10979     DisplayTitle(buf);
10980     if (first.sendName) {
10981       sprintf(buf, "name %s\n", gameInfo.black);
10982       SendToProgram(buf, &first);
10983     }
10984     if (first.sendTime) {
10985       if (first.useColors) {
10986         SendToProgram("black\n", &first); /*gnu kludge*/
10987       }
10988       SendTimeRemaining(&first, TRUE);
10989     }
10990     if (first.useColors) {
10991       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10992     }
10993     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10994     SetMachineThinkingEnables();
10995     first.maybeThinking = TRUE;
10996     StartClocks();
10997     firstMove = FALSE;
10998
10999     if (appData.autoFlipView && !flipView) {
11000       flipView = !flipView;
11001       DrawPosition(FALSE, NULL);
11002       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11003     }
11004
11005     if(bookHit) { // [HGM] book: simulate book reply
11006         static char bookMove[MSG_SIZ]; // a bit generous?
11007
11008         programStats.nodes = programStats.depth = programStats.time =
11009         programStats.score = programStats.got_only_move = 0;
11010         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11011
11012         strcpy(bookMove, "move ");
11013         strcat(bookMove, bookHit);
11014         HandleMachineMove(bookMove, &first);
11015     }
11016 }
11017
11018 void
11019 MachineBlackEvent()
11020 {
11021     char buf[MSG_SIZ];
11022    char *bookHit = NULL;
11023
11024     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11025         return;
11026
11027
11028     if (gameMode == PlayFromGameFile ||
11029         gameMode == TwoMachinesPlay  ||
11030         gameMode == Training         ||
11031         gameMode == AnalyzeMode      ||
11032         gameMode == EndOfGame)
11033         EditGameEvent();
11034
11035     if (gameMode == EditPosition) 
11036         EditPositionDone(TRUE);
11037
11038     if (WhiteOnMove(currentMove)) {
11039         DisplayError(_("It is not Black's turn"), 0);
11040         return;
11041     }
11042
11043     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11044       ExitAnalyzeMode();
11045
11046     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11047         gameMode == AnalyzeFile)
11048         TruncateGame();
11049
11050     ResurrectChessProgram();    /* in case it isn't running */
11051     gameMode = MachinePlaysBlack;
11052     pausing = FALSE;
11053     ModeHighlight();
11054     SetGameInfo();
11055     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11056     DisplayTitle(buf);
11057     if (first.sendName) {
11058       sprintf(buf, "name %s\n", gameInfo.white);
11059       SendToProgram(buf, &first);
11060     }
11061     if (first.sendTime) {
11062       if (first.useColors) {
11063         SendToProgram("white\n", &first); /*gnu kludge*/
11064       }
11065       SendTimeRemaining(&first, FALSE);
11066     }
11067     if (first.useColors) {
11068       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11069     }
11070     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11071     SetMachineThinkingEnables();
11072     first.maybeThinking = TRUE;
11073     StartClocks();
11074
11075     if (appData.autoFlipView && flipView) {
11076       flipView = !flipView;
11077       DrawPosition(FALSE, NULL);
11078       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11079     }
11080     if(bookHit) { // [HGM] book: simulate book reply
11081         static char bookMove[MSG_SIZ]; // a bit generous?
11082
11083         programStats.nodes = programStats.depth = programStats.time =
11084         programStats.score = programStats.got_only_move = 0;
11085         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11086
11087         strcpy(bookMove, "move ");
11088         strcat(bookMove, bookHit);
11089         HandleMachineMove(bookMove, &first);
11090     }
11091 }
11092
11093
11094 void
11095 DisplayTwoMachinesTitle()
11096 {
11097     char buf[MSG_SIZ];
11098     if (appData.matchGames > 0) {
11099         if (first.twoMachinesColor[0] == 'w') {
11100             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11101                     gameInfo.white, gameInfo.black,
11102                     first.matchWins, second.matchWins,
11103                     matchGame - 1 - (first.matchWins + second.matchWins));
11104         } else {
11105             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11106                     gameInfo.white, gameInfo.black,
11107                     second.matchWins, first.matchWins,
11108                     matchGame - 1 - (first.matchWins + second.matchWins));
11109         }
11110     } else {
11111         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11112     }
11113     DisplayTitle(buf);
11114 }
11115
11116 void
11117 TwoMachinesEvent P((void))
11118 {
11119     int i;
11120     char buf[MSG_SIZ];
11121     ChessProgramState *onmove;
11122     char *bookHit = NULL;
11123
11124     if (appData.noChessProgram) return;
11125
11126     switch (gameMode) {
11127       case TwoMachinesPlay:
11128         return;
11129       case MachinePlaysWhite:
11130       case MachinePlaysBlack:
11131         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11132             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11133             return;
11134         }
11135         /* fall through */
11136       case BeginningOfGame:
11137       case PlayFromGameFile:
11138       case EndOfGame:
11139         EditGameEvent();
11140         if (gameMode != EditGame) return;
11141         break;
11142       case EditPosition:
11143         EditPositionDone(TRUE);
11144         break;
11145       case AnalyzeMode:
11146       case AnalyzeFile:
11147         ExitAnalyzeMode();
11148         break;
11149       case EditGame:
11150       default:
11151         break;
11152     }
11153
11154 //    forwardMostMove = currentMove;
11155     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11156     ResurrectChessProgram();    /* in case first program isn't running */
11157
11158     if (second.pr == NULL) {
11159         StartChessProgram(&second);
11160         if (second.protocolVersion == 1) {
11161           TwoMachinesEventIfReady();
11162         } else {
11163           /* kludge: allow timeout for initial "feature" command */
11164           FreezeUI();
11165           DisplayMessage("", _("Starting second chess program"));
11166           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11167         }
11168         return;
11169     }
11170     DisplayMessage("", "");
11171     InitChessProgram(&second, FALSE);
11172     SendToProgram("force\n", &second);
11173     if (startedFromSetupPosition) {
11174         SendBoard(&second, backwardMostMove);
11175     if (appData.debugMode) {
11176         fprintf(debugFP, "Two Machines\n");
11177     }
11178     }
11179     for (i = backwardMostMove; i < forwardMostMove; i++) {
11180         SendMoveToProgram(i, &second);
11181     }
11182
11183     gameMode = TwoMachinesPlay;
11184     pausing = FALSE;
11185     ModeHighlight();
11186     SetGameInfo();
11187     DisplayTwoMachinesTitle();
11188     firstMove = TRUE;
11189     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11190         onmove = &first;
11191     } else {
11192         onmove = &second;
11193     }
11194
11195     SendToProgram(first.computerString, &first);
11196     if (first.sendName) {
11197       sprintf(buf, "name %s\n", second.tidy);
11198       SendToProgram(buf, &first);
11199     }
11200     SendToProgram(second.computerString, &second);
11201     if (second.sendName) {
11202       sprintf(buf, "name %s\n", first.tidy);
11203       SendToProgram(buf, &second);
11204     }
11205
11206     ResetClocks();
11207     if (!first.sendTime || !second.sendTime) {
11208         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11209         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11210     }
11211     if (onmove->sendTime) {
11212       if (onmove->useColors) {
11213         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11214       }
11215       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11216     }
11217     if (onmove->useColors) {
11218       SendToProgram(onmove->twoMachinesColor, onmove);
11219     }
11220     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11221 //    SendToProgram("go\n", onmove);
11222     onmove->maybeThinking = TRUE;
11223     SetMachineThinkingEnables();
11224
11225     StartClocks();
11226
11227     if(bookHit) { // [HGM] book: simulate book reply
11228         static char bookMove[MSG_SIZ]; // a bit generous?
11229
11230         programStats.nodes = programStats.depth = programStats.time =
11231         programStats.score = programStats.got_only_move = 0;
11232         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11233
11234         strcpy(bookMove, "move ");
11235         strcat(bookMove, bookHit);
11236         savedMessage = bookMove; // args for deferred call
11237         savedState = onmove;
11238         ScheduleDelayedEvent(DeferredBookMove, 1);
11239     }
11240 }
11241
11242 void
11243 TrainingEvent()
11244 {
11245     if (gameMode == Training) {
11246       SetTrainingModeOff();
11247       gameMode = PlayFromGameFile;
11248       DisplayMessage("", _("Training mode off"));
11249     } else {
11250       gameMode = Training;
11251       animateTraining = appData.animate;
11252
11253       /* make sure we are not already at the end of the game */
11254       if (currentMove < forwardMostMove) {
11255         SetTrainingModeOn();
11256         DisplayMessage("", _("Training mode on"));
11257       } else {
11258         gameMode = PlayFromGameFile;
11259         DisplayError(_("Already at end of game"), 0);
11260       }
11261     }
11262     ModeHighlight();
11263 }
11264
11265 void
11266 IcsClientEvent()
11267 {
11268     if (!appData.icsActive) return;
11269     switch (gameMode) {
11270       case IcsPlayingWhite:
11271       case IcsPlayingBlack:
11272       case IcsObserving:
11273       case IcsIdle:
11274       case BeginningOfGame:
11275       case IcsExamining:
11276         return;
11277
11278       case EditGame:
11279         break;
11280
11281       case EditPosition:
11282         EditPositionDone(TRUE);
11283         break;
11284
11285       case AnalyzeMode:
11286       case AnalyzeFile:
11287         ExitAnalyzeMode();
11288         break;
11289
11290       default:
11291         EditGameEvent();
11292         break;
11293     }
11294
11295     gameMode = IcsIdle;
11296     ModeHighlight();
11297     return;
11298 }
11299
11300
11301 void
11302 EditGameEvent()
11303 {
11304     int i;
11305
11306     switch (gameMode) {
11307       case Training:
11308         SetTrainingModeOff();
11309         break;
11310       case MachinePlaysWhite:
11311       case MachinePlaysBlack:
11312       case BeginningOfGame:
11313         SendToProgram("force\n", &first);
11314         SetUserThinkingEnables();
11315         break;
11316       case PlayFromGameFile:
11317         (void) StopLoadGameTimer();
11318         if (gameFileFP != NULL) {
11319             gameFileFP = NULL;
11320         }
11321         break;
11322       case EditPosition:
11323         EditPositionDone(TRUE);
11324         break;
11325       case AnalyzeMode:
11326       case AnalyzeFile:
11327         ExitAnalyzeMode();
11328         SendToProgram("force\n", &first);
11329         break;
11330       case TwoMachinesPlay:
11331         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11332         ResurrectChessProgram();
11333         SetUserThinkingEnables();
11334         break;
11335       case EndOfGame:
11336         ResurrectChessProgram();
11337         break;
11338       case IcsPlayingBlack:
11339       case IcsPlayingWhite:
11340         DisplayError(_("Warning: You are still playing a game"), 0);
11341         break;
11342       case IcsObserving:
11343         DisplayError(_("Warning: You are still observing a game"), 0);
11344         break;
11345       case IcsExamining:
11346         DisplayError(_("Warning: You are still examining a game"), 0);
11347         break;
11348       case IcsIdle:
11349         break;
11350       case EditGame:
11351       default:
11352         return;
11353     }
11354
11355     pausing = FALSE;
11356     StopClocks();
11357     first.offeredDraw = second.offeredDraw = 0;
11358
11359     if (gameMode == PlayFromGameFile) {
11360         whiteTimeRemaining = timeRemaining[0][currentMove];
11361         blackTimeRemaining = timeRemaining[1][currentMove];
11362         DisplayTitle("");
11363     }
11364
11365     if (gameMode == MachinePlaysWhite ||
11366         gameMode == MachinePlaysBlack ||
11367         gameMode == TwoMachinesPlay ||
11368         gameMode == EndOfGame) {
11369         i = forwardMostMove;
11370         while (i > currentMove) {
11371             SendToProgram("undo\n", &first);
11372             i--;
11373         }
11374         whiteTimeRemaining = timeRemaining[0][currentMove];
11375         blackTimeRemaining = timeRemaining[1][currentMove];
11376         DisplayBothClocks();
11377         if (whiteFlag || blackFlag) {
11378             whiteFlag = blackFlag = 0;
11379         }
11380         DisplayTitle("");
11381     }
11382
11383     gameMode = EditGame;
11384     ModeHighlight();
11385     SetGameInfo();
11386 }
11387
11388
11389 void
11390 EditPositionEvent()
11391 {
11392     if (gameMode == EditPosition) {
11393         EditGameEvent();
11394         return;
11395     }
11396
11397     EditGameEvent();
11398     if (gameMode != EditGame) return;
11399
11400     gameMode = EditPosition;
11401     ModeHighlight();
11402     SetGameInfo();
11403     if (currentMove > 0)
11404       CopyBoard(boards[0], boards[currentMove]);
11405
11406     blackPlaysFirst = !WhiteOnMove(currentMove);
11407     ResetClocks();
11408     currentMove = forwardMostMove = backwardMostMove = 0;
11409     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11410     DisplayMove(-1);
11411 }
11412
11413 void
11414 ExitAnalyzeMode()
11415 {
11416     /* [DM] icsEngineAnalyze - possible call from other functions */
11417     if (appData.icsEngineAnalyze) {
11418         appData.icsEngineAnalyze = FALSE;
11419
11420         DisplayMessage("",_("Close ICS engine analyze..."));
11421     }
11422     if (first.analysisSupport && first.analyzing) {
11423       SendToProgram("exit\n", &first);
11424       first.analyzing = FALSE;
11425     }
11426     thinkOutput[0] = NULLCHAR;
11427 }
11428
11429 void
11430 EditPositionDone(Boolean fakeRights)
11431 {
11432     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11433
11434     startedFromSetupPosition = TRUE;
11435     InitChessProgram(&first, FALSE);
11436     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11437       boards[0][EP_STATUS] = EP_NONE;
11438       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11439     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11440         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11441         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11442       } else boards[0][CASTLING][2] = NoRights;
11443     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11444         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11445         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11446       } else boards[0][CASTLING][5] = NoRights;
11447     }
11448     SendToProgram("force\n", &first);
11449     if (blackPlaysFirst) {
11450         strcpy(moveList[0], "");
11451         strcpy(parseList[0], "");
11452         currentMove = forwardMostMove = backwardMostMove = 1;
11453         CopyBoard(boards[1], boards[0]);
11454     } else {
11455         currentMove = forwardMostMove = backwardMostMove = 0;
11456     }
11457     SendBoard(&first, forwardMostMove);
11458     if (appData.debugMode) {
11459         fprintf(debugFP, "EditPosDone\n");
11460     }
11461     DisplayTitle("");
11462     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11463     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11464     gameMode = EditGame;
11465     ModeHighlight();
11466     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11467     ClearHighlights(); /* [AS] */
11468 }
11469
11470 /* Pause for `ms' milliseconds */
11471 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11472 void
11473 TimeDelay(ms)
11474      long ms;
11475 {
11476     TimeMark m1, m2;
11477
11478     GetTimeMark(&m1);
11479     do {
11480         GetTimeMark(&m2);
11481     } while (SubtractTimeMarks(&m2, &m1) < ms);
11482 }
11483
11484 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11485 void
11486 SendMultiLineToICS(buf)
11487      char *buf;
11488 {
11489     char temp[MSG_SIZ+1], *p;
11490     int len;
11491
11492     len = strlen(buf);
11493     if (len > MSG_SIZ)
11494       len = MSG_SIZ;
11495
11496     strncpy(temp, buf, len);
11497     temp[len] = 0;
11498
11499     p = temp;
11500     while (*p) {
11501         if (*p == '\n' || *p == '\r')
11502           *p = ' ';
11503         ++p;
11504     }
11505
11506     strcat(temp, "\n");
11507     SendToICS(temp);
11508     SendToPlayer(temp, strlen(temp));
11509 }
11510
11511 void
11512 SetWhiteToPlayEvent()
11513 {
11514     if (gameMode == EditPosition) {
11515         blackPlaysFirst = FALSE;
11516         DisplayBothClocks();    /* works because currentMove is 0 */
11517     } else if (gameMode == IcsExamining) {
11518         SendToICS(ics_prefix);
11519         SendToICS("tomove white\n");
11520     }
11521 }
11522
11523 void
11524 SetBlackToPlayEvent()
11525 {
11526     if (gameMode == EditPosition) {
11527         blackPlaysFirst = TRUE;
11528         currentMove = 1;        /* kludge */
11529         DisplayBothClocks();
11530         currentMove = 0;
11531     } else if (gameMode == IcsExamining) {
11532         SendToICS(ics_prefix);
11533         SendToICS("tomove black\n");
11534     }
11535 }
11536
11537 void
11538 EditPositionMenuEvent(selection, x, y)
11539      ChessSquare selection;
11540      int x, y;
11541 {
11542     char buf[MSG_SIZ];
11543     ChessSquare piece = boards[0][y][x];
11544
11545     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11546
11547     switch (selection) {
11548       case ClearBoard:
11549         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11550             SendToICS(ics_prefix);
11551             SendToICS("bsetup clear\n");
11552         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11553             SendToICS(ics_prefix);
11554             SendToICS("clearboard\n");
11555         } else {
11556             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11557                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11558                 for (y = 0; y < BOARD_HEIGHT; y++) {
11559                     if (gameMode == IcsExamining) {
11560                         if (boards[currentMove][y][x] != EmptySquare) {
11561                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11562                                     AAA + x, ONE + y);
11563                             SendToICS(buf);
11564                         }
11565                     } else {
11566                         boards[0][y][x] = p;
11567                     }
11568                 }
11569             }
11570         }
11571         if (gameMode == EditPosition) {
11572             DrawPosition(FALSE, boards[0]);
11573         }
11574         break;
11575
11576       case WhitePlay:
11577         SetWhiteToPlayEvent();
11578         break;
11579
11580       case BlackPlay:
11581         SetBlackToPlayEvent();
11582         break;
11583
11584       case EmptySquare:
11585         if (gameMode == IcsExamining) {
11586             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11587             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11588             SendToICS(buf);
11589         } else {
11590             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11591                 if(x == BOARD_LEFT-2) {
11592                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11593                     boards[0][y][1] = 0;
11594                 } else
11595                 if(x == BOARD_RGHT+1) {
11596                     if(y >= gameInfo.holdingsSize) break;
11597                     boards[0][y][BOARD_WIDTH-2] = 0;
11598                 } else break;
11599             }
11600             boards[0][y][x] = EmptySquare;
11601             DrawPosition(FALSE, boards[0]);
11602         }
11603         break;
11604
11605       case PromotePiece:
11606         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11607            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11608             selection = (ChessSquare) (PROMOTED piece);
11609         } else if(piece == EmptySquare) selection = WhiteSilver;
11610         else selection = (ChessSquare)((int)piece - 1);
11611         goto defaultlabel;
11612
11613       case DemotePiece:
11614         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11615            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11616             selection = (ChessSquare) (DEMOTED piece);
11617         } else if(piece == EmptySquare) selection = BlackSilver;
11618         else selection = (ChessSquare)((int)piece + 1);
11619         goto defaultlabel;
11620
11621       case WhiteQueen:
11622       case BlackQueen:
11623         if(gameInfo.variant == VariantShatranj ||
11624            gameInfo.variant == VariantXiangqi  ||
11625            gameInfo.variant == VariantCourier  ||
11626            gameInfo.variant == VariantMakruk     )
11627             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11628         goto defaultlabel;
11629
11630       case WhiteKing:
11631       case BlackKing:
11632         if(gameInfo.variant == VariantXiangqi)
11633             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11634         if(gameInfo.variant == VariantKnightmate)
11635             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11636       default:
11637         defaultlabel:
11638         if (gameMode == IcsExamining) {
11639             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11640             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11641                     PieceToChar(selection), AAA + x, ONE + y);
11642             SendToICS(buf);
11643         } else {
11644             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11645                 int n;
11646                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11647                     n = PieceToNumber(selection - BlackPawn);
11648                     if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11649                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11650                     boards[0][BOARD_HEIGHT-1-n][1]++;
11651                 } else
11652                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11653                     n = PieceToNumber(selection);
11654                     if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11655                     boards[0][n][BOARD_WIDTH-1] = selection;
11656                     boards[0][n][BOARD_WIDTH-2]++;
11657                 }
11658             } else
11659             boards[0][y][x] = selection;
11660             DrawPosition(TRUE, boards[0]);
11661         }
11662         break;
11663     }
11664 }
11665
11666
11667 void
11668 DropMenuEvent(selection, x, y)
11669      ChessSquare selection;
11670      int x, y;
11671 {
11672     ChessMove moveType;
11673
11674     switch (gameMode) {
11675       case IcsPlayingWhite:
11676       case MachinePlaysBlack:
11677         if (!WhiteOnMove(currentMove)) {
11678             DisplayMoveError(_("It is Black's turn"));
11679             return;
11680         }
11681         moveType = WhiteDrop;
11682         break;
11683       case IcsPlayingBlack:
11684       case MachinePlaysWhite:
11685         if (WhiteOnMove(currentMove)) {
11686             DisplayMoveError(_("It is White's turn"));
11687             return;
11688         }
11689         moveType = BlackDrop;
11690         break;
11691       case EditGame:
11692         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11693         break;
11694       default:
11695         return;
11696     }
11697
11698     if (moveType == BlackDrop && selection < BlackPawn) {
11699       selection = (ChessSquare) ((int) selection
11700                                  + (int) BlackPawn - (int) WhitePawn);
11701     }
11702     if (boards[currentMove][y][x] != EmptySquare) {
11703         DisplayMoveError(_("That square is occupied"));
11704         return;
11705     }
11706
11707     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11708 }
11709
11710 void
11711 AcceptEvent()
11712 {
11713     /* Accept a pending offer of any kind from opponent */
11714
11715     if (appData.icsActive) {
11716         SendToICS(ics_prefix);
11717         SendToICS("accept\n");
11718     } else if (cmailMsgLoaded) {
11719         if (currentMove == cmailOldMove &&
11720             commentList[cmailOldMove] != NULL &&
11721             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11722                    "Black offers a draw" : "White offers a draw")) {
11723             TruncateGame();
11724             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11725             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11726         } else {
11727             DisplayError(_("There is no pending offer on this move"), 0);
11728             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11729         }
11730     } else {
11731         /* Not used for offers from chess program */
11732     }
11733 }
11734
11735 void
11736 DeclineEvent()
11737 {
11738     /* Decline a pending offer of any kind from opponent */
11739
11740     if (appData.icsActive) {
11741         SendToICS(ics_prefix);
11742         SendToICS("decline\n");
11743     } else if (cmailMsgLoaded) {
11744         if (currentMove == cmailOldMove &&
11745             commentList[cmailOldMove] != NULL &&
11746             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11747                    "Black offers a draw" : "White offers a draw")) {
11748 #ifdef NOTDEF
11749             AppendComment(cmailOldMove, "Draw declined", TRUE);
11750             DisplayComment(cmailOldMove - 1, "Draw declined");
11751 #endif /*NOTDEF*/
11752         } else {
11753             DisplayError(_("There is no pending offer on this move"), 0);
11754         }
11755     } else {
11756         /* Not used for offers from chess program */
11757     }
11758 }
11759
11760 void
11761 RematchEvent()
11762 {
11763     /* Issue ICS rematch command */
11764     if (appData.icsActive) {
11765         SendToICS(ics_prefix);
11766         SendToICS("rematch\n");
11767     }
11768 }
11769
11770 void
11771 CallFlagEvent()
11772 {
11773     /* Call your opponent's flag (claim a win on time) */
11774     if (appData.icsActive) {
11775         SendToICS(ics_prefix);
11776         SendToICS("flag\n");
11777     } else {
11778         switch (gameMode) {
11779           default:
11780             return;
11781           case MachinePlaysWhite:
11782             if (whiteFlag) {
11783                 if (blackFlag)
11784                   GameEnds(GameIsDrawn, "Both players ran out of time",
11785                            GE_PLAYER);
11786                 else
11787                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11788             } else {
11789                 DisplayError(_("Your opponent is not out of time"), 0);
11790             }
11791             break;
11792           case MachinePlaysBlack:
11793             if (blackFlag) {
11794                 if (whiteFlag)
11795                   GameEnds(GameIsDrawn, "Both players ran out of time",
11796                            GE_PLAYER);
11797                 else
11798                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11799             } else {
11800                 DisplayError(_("Your opponent is not out of time"), 0);
11801             }
11802             break;
11803         }
11804     }
11805 }
11806
11807 void
11808 DrawEvent()
11809 {
11810     /* Offer draw or accept pending draw offer from opponent */
11811
11812     if (appData.icsActive) {
11813         /* Note: tournament rules require draw offers to be
11814            made after you make your move but before you punch
11815            your clock.  Currently ICS doesn't let you do that;
11816            instead, you immediately punch your clock after making
11817            a move, but you can offer a draw at any time. */
11818
11819         SendToICS(ics_prefix);
11820         SendToICS("draw\n");
11821     } else if (cmailMsgLoaded) {
11822         if (currentMove == cmailOldMove &&
11823             commentList[cmailOldMove] != NULL &&
11824             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11825                    "Black offers a draw" : "White offers a draw")) {
11826             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11827             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11828         } else if (currentMove == cmailOldMove + 1) {
11829             char *offer = WhiteOnMove(cmailOldMove) ?
11830               "White offers a draw" : "Black offers a draw";
11831             AppendComment(currentMove, offer, TRUE);
11832             DisplayComment(currentMove - 1, offer);
11833             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11834         } else {
11835             DisplayError(_("You must make your move before offering a draw"), 0);
11836             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11837         }
11838     } else if (first.offeredDraw) {
11839         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11840     } else {
11841         if (first.sendDrawOffers) {
11842             SendToProgram("draw\n", &first);
11843             userOfferedDraw = TRUE;
11844         }
11845     }
11846 }
11847
11848 void
11849 AdjournEvent()
11850 {
11851     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11852
11853     if (appData.icsActive) {
11854         SendToICS(ics_prefix);
11855         SendToICS("adjourn\n");
11856     } else {
11857         /* Currently GNU Chess doesn't offer or accept Adjourns */
11858     }
11859 }
11860
11861
11862 void
11863 AbortEvent()
11864 {
11865     /* Offer Abort or accept pending Abort offer from opponent */
11866
11867     if (appData.icsActive) {
11868         SendToICS(ics_prefix);
11869         SendToICS("abort\n");
11870     } else {
11871         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11872     }
11873 }
11874
11875 void
11876 ResignEvent()
11877 {
11878     /* Resign.  You can do this even if it's not your turn. */
11879
11880     if (appData.icsActive) {
11881         SendToICS(ics_prefix);
11882         SendToICS("resign\n");
11883     } else {
11884         switch (gameMode) {
11885           case MachinePlaysWhite:
11886             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11887             break;
11888           case MachinePlaysBlack:
11889             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11890             break;
11891           case EditGame:
11892             if (cmailMsgLoaded) {
11893                 TruncateGame();
11894                 if (WhiteOnMove(cmailOldMove)) {
11895                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11896                 } else {
11897                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11898                 }
11899                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11900             }
11901             break;
11902           default:
11903             break;
11904         }
11905     }
11906 }
11907
11908
11909 void
11910 StopObservingEvent()
11911 {
11912     /* Stop observing current games */
11913     SendToICS(ics_prefix);
11914     SendToICS("unobserve\n");
11915 }
11916
11917 void
11918 StopExaminingEvent()
11919 {
11920     /* Stop observing current game */
11921     SendToICS(ics_prefix);
11922     SendToICS("unexamine\n");
11923 }
11924
11925 void
11926 ForwardInner(target)
11927      int target;
11928 {
11929     int limit;
11930
11931     if (appData.debugMode)
11932         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11933                 target, currentMove, forwardMostMove);
11934
11935     if (gameMode == EditPosition)
11936       return;
11937
11938     if (gameMode == PlayFromGameFile && !pausing)
11939       PauseEvent();
11940
11941     if (gameMode == IcsExamining && pausing)
11942       limit = pauseExamForwardMostMove;
11943     else
11944       limit = forwardMostMove;
11945
11946     if (target > limit) target = limit;
11947
11948     if (target > 0 && moveList[target - 1][0]) {
11949         int fromX, fromY, toX, toY;
11950         toX = moveList[target - 1][2] - AAA;
11951         toY = moveList[target - 1][3] - ONE;
11952         if (moveList[target - 1][1] == '@') {
11953             if (appData.highlightLastMove) {
11954                 SetHighlights(-1, -1, toX, toY);
11955             }
11956         } else {
11957             fromX = moveList[target - 1][0] - AAA;
11958             fromY = moveList[target - 1][1] - ONE;
11959             if (target == currentMove + 1) {
11960                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11961             }
11962             if (appData.highlightLastMove) {
11963                 SetHighlights(fromX, fromY, toX, toY);
11964             }
11965         }
11966     }
11967     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11968         gameMode == Training || gameMode == PlayFromGameFile ||
11969         gameMode == AnalyzeFile) {
11970         while (currentMove < target) {
11971             SendMoveToProgram(currentMove++, &first);
11972         }
11973     } else {
11974         currentMove = target;
11975     }
11976
11977     if (gameMode == EditGame || gameMode == EndOfGame) {
11978         whiteTimeRemaining = timeRemaining[0][currentMove];
11979         blackTimeRemaining = timeRemaining[1][currentMove];
11980     }
11981     DisplayBothClocks();
11982     DisplayMove(currentMove - 1);
11983     DrawPosition(FALSE, boards[currentMove]);
11984     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11985     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11986         DisplayComment(currentMove - 1, commentList[currentMove]);
11987     }
11988 }
11989
11990
11991 void
11992 ForwardEvent()
11993 {
11994     if (gameMode == IcsExamining && !pausing) {
11995         SendToICS(ics_prefix);
11996         SendToICS("forward\n");
11997     } else {
11998         ForwardInner(currentMove + 1);
11999     }
12000 }
12001
12002 void
12003 ToEndEvent()
12004 {
12005     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12006         /* to optimze, we temporarily turn off analysis mode while we feed
12007          * the remaining moves to the engine. Otherwise we get analysis output
12008          * after each move.
12009          */
12010         if (first.analysisSupport) {
12011           SendToProgram("exit\nforce\n", &first);
12012           first.analyzing = FALSE;
12013         }
12014     }
12015
12016     if (gameMode == IcsExamining && !pausing) {
12017         SendToICS(ics_prefix);
12018         SendToICS("forward 999999\n");
12019     } else {
12020         ForwardInner(forwardMostMove);
12021     }
12022
12023     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12024         /* we have fed all the moves, so reactivate analysis mode */
12025         SendToProgram("analyze\n", &first);
12026         first.analyzing = TRUE;
12027         /*first.maybeThinking = TRUE;*/
12028         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12029     }
12030 }
12031
12032 void
12033 BackwardInner(target)
12034      int target;
12035 {
12036     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12037
12038     if (appData.debugMode)
12039         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12040                 target, currentMove, forwardMostMove);
12041
12042     if (gameMode == EditPosition) return;
12043     if (currentMove <= backwardMostMove) {
12044         ClearHighlights();
12045         DrawPosition(full_redraw, boards[currentMove]);
12046         return;
12047     }
12048     if (gameMode == PlayFromGameFile && !pausing)
12049       PauseEvent();
12050
12051     if (moveList[target][0]) {
12052         int fromX, fromY, toX, toY;
12053         toX = moveList[target][2] - AAA;
12054         toY = moveList[target][3] - ONE;
12055         if (moveList[target][1] == '@') {
12056             if (appData.highlightLastMove) {
12057                 SetHighlights(-1, -1, toX, toY);
12058             }
12059         } else {
12060             fromX = moveList[target][0] - AAA;
12061             fromY = moveList[target][1] - ONE;
12062             if (target == currentMove - 1) {
12063                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12064             }
12065             if (appData.highlightLastMove) {
12066                 SetHighlights(fromX, fromY, toX, toY);
12067             }
12068         }
12069     }
12070     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12071         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12072         while (currentMove > target) {
12073             SendToProgram("undo\n", &first);
12074             currentMove--;
12075         }
12076     } else {
12077         currentMove = target;
12078     }
12079
12080     if (gameMode == EditGame || gameMode == EndOfGame) {
12081         whiteTimeRemaining = timeRemaining[0][currentMove];
12082         blackTimeRemaining = timeRemaining[1][currentMove];
12083     }
12084     DisplayBothClocks();
12085     DisplayMove(currentMove - 1);
12086     DrawPosition(full_redraw, boards[currentMove]);
12087     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12088     // [HGM] PV info: routine tests if comment empty
12089     DisplayComment(currentMove - 1, commentList[currentMove]);
12090 }
12091
12092 void
12093 BackwardEvent()
12094 {
12095     if (gameMode == IcsExamining && !pausing) {
12096         SendToICS(ics_prefix);
12097         SendToICS("backward\n");
12098     } else {
12099         BackwardInner(currentMove - 1);
12100     }
12101 }
12102
12103 void
12104 ToStartEvent()
12105 {
12106     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12107         /* to optimize, we temporarily turn off analysis mode while we undo
12108          * all the moves. Otherwise we get analysis output after each undo.
12109          */
12110         if (first.analysisSupport) {
12111           SendToProgram("exit\nforce\n", &first);
12112           first.analyzing = FALSE;
12113         }
12114     }
12115
12116     if (gameMode == IcsExamining && !pausing) {
12117         SendToICS(ics_prefix);
12118         SendToICS("backward 999999\n");
12119     } else {
12120         BackwardInner(backwardMostMove);
12121     }
12122
12123     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12124         /* we have fed all the moves, so reactivate analysis mode */
12125         SendToProgram("analyze\n", &first);
12126         first.analyzing = TRUE;
12127         /*first.maybeThinking = TRUE;*/
12128         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12129     }
12130 }
12131
12132 void
12133 ToNrEvent(int to)
12134 {
12135   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12136   if (to >= forwardMostMove) to = forwardMostMove;
12137   if (to <= backwardMostMove) to = backwardMostMove;
12138   if (to < currentMove) {
12139     BackwardInner(to);
12140   } else {
12141     ForwardInner(to);
12142   }
12143 }
12144
12145 void
12146 RevertEvent()
12147 {
12148     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12149         return;
12150     }
12151     if (gameMode != IcsExamining) {
12152         DisplayError(_("You are not examining a game"), 0);
12153         return;
12154     }
12155     if (pausing) {
12156         DisplayError(_("You can't revert while pausing"), 0);
12157         return;
12158     }
12159     SendToICS(ics_prefix);
12160     SendToICS("revert\n");
12161 }
12162
12163 void
12164 RetractMoveEvent()
12165 {
12166     switch (gameMode) {
12167       case MachinePlaysWhite:
12168       case MachinePlaysBlack:
12169         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12170             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12171             return;
12172         }
12173         if (forwardMostMove < 2) return;
12174         currentMove = forwardMostMove = forwardMostMove - 2;
12175         whiteTimeRemaining = timeRemaining[0][currentMove];
12176         blackTimeRemaining = timeRemaining[1][currentMove];
12177         DisplayBothClocks();
12178         DisplayMove(currentMove - 1);
12179         ClearHighlights();/*!! could figure this out*/
12180         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12181         SendToProgram("remove\n", &first);
12182         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12183         break;
12184
12185       case BeginningOfGame:
12186       default:
12187         break;
12188
12189       case IcsPlayingWhite:
12190       case IcsPlayingBlack:
12191         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12192             SendToICS(ics_prefix);
12193             SendToICS("takeback 2\n");
12194         } else {
12195             SendToICS(ics_prefix);
12196             SendToICS("takeback 1\n");
12197         }
12198         break;
12199     }
12200 }
12201
12202 void
12203 MoveNowEvent()
12204 {
12205     ChessProgramState *cps;
12206
12207     switch (gameMode) {
12208       case MachinePlaysWhite:
12209         if (!WhiteOnMove(forwardMostMove)) {
12210             DisplayError(_("It is your turn"), 0);
12211             return;
12212         }
12213         cps = &first;
12214         break;
12215       case MachinePlaysBlack:
12216         if (WhiteOnMove(forwardMostMove)) {
12217             DisplayError(_("It is your turn"), 0);
12218             return;
12219         }
12220         cps = &first;
12221         break;
12222       case TwoMachinesPlay:
12223         if (WhiteOnMove(forwardMostMove) ==
12224             (first.twoMachinesColor[0] == 'w')) {
12225             cps = &first;
12226         } else {
12227             cps = &second;
12228         }
12229         break;
12230       case BeginningOfGame:
12231       default:
12232         return;
12233     }
12234     SendToProgram("?\n", cps);
12235 }
12236
12237 void
12238 TruncateGameEvent()
12239 {
12240     EditGameEvent();
12241     if (gameMode != EditGame) return;
12242     TruncateGame();
12243 }
12244
12245 void
12246 TruncateGame()
12247 {
12248     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12249     if (forwardMostMove > currentMove) {
12250         if (gameInfo.resultDetails != NULL) {
12251             free(gameInfo.resultDetails);
12252             gameInfo.resultDetails = NULL;
12253             gameInfo.result = GameUnfinished;
12254         }
12255         forwardMostMove = currentMove;
12256         HistorySet(parseList, backwardMostMove, forwardMostMove,
12257                    currentMove-1);
12258     }
12259 }
12260
12261 void
12262 HintEvent()
12263 {
12264     if (appData.noChessProgram) return;
12265     switch (gameMode) {
12266       case MachinePlaysWhite:
12267         if (WhiteOnMove(forwardMostMove)) {
12268             DisplayError(_("Wait until your turn"), 0);
12269             return;
12270         }
12271         break;
12272       case BeginningOfGame:
12273       case MachinePlaysBlack:
12274         if (!WhiteOnMove(forwardMostMove)) {
12275             DisplayError(_("Wait until your turn"), 0);
12276             return;
12277         }
12278         break;
12279       default:
12280         DisplayError(_("No hint available"), 0);
12281         return;
12282     }
12283     SendToProgram("hint\n", &first);
12284     hintRequested = TRUE;
12285 }
12286
12287 void
12288 BookEvent()
12289 {
12290     if (appData.noChessProgram) return;
12291     switch (gameMode) {
12292       case MachinePlaysWhite:
12293         if (WhiteOnMove(forwardMostMove)) {
12294             DisplayError(_("Wait until your turn"), 0);
12295             return;
12296         }
12297         break;
12298       case BeginningOfGame:
12299       case MachinePlaysBlack:
12300         if (!WhiteOnMove(forwardMostMove)) {
12301             DisplayError(_("Wait until your turn"), 0);
12302             return;
12303         }
12304         break;
12305       case EditPosition:
12306         EditPositionDone(TRUE);
12307         break;
12308       case TwoMachinesPlay:
12309         return;
12310       default:
12311         break;
12312     }
12313     SendToProgram("bk\n", &first);
12314     bookOutput[0] = NULLCHAR;
12315     bookRequested = TRUE;
12316 }
12317
12318 void
12319 AboutGameEvent()
12320 {
12321     char *tags = PGNTags(&gameInfo);
12322     TagsPopUp(tags, CmailMsg());
12323     free(tags);
12324 }
12325
12326 /* end button procedures */
12327
12328 void
12329 PrintPosition(fp, move)
12330      FILE *fp;
12331      int move;
12332 {
12333     int i, j;
12334
12335     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12336         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12337             char c = PieceToChar(boards[move][i][j]);
12338             fputc(c == 'x' ? '.' : c, fp);
12339             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12340         }
12341     }
12342     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12343       fprintf(fp, "white to play\n");
12344     else
12345       fprintf(fp, "black to play\n");
12346 }
12347
12348 void
12349 PrintOpponents(fp)
12350      FILE *fp;
12351 {
12352     if (gameInfo.white != NULL) {
12353         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12354     } else {
12355         fprintf(fp, "\n");
12356     }
12357 }
12358
12359 /* Find last component of program's own name, using some heuristics */
12360 void
12361 TidyProgramName(prog, host, buf)
12362      char *prog, *host, buf[MSG_SIZ];
12363 {
12364     char *p, *q;
12365     int local = (strcmp(host, "localhost") == 0);
12366     while (!local && (p = strchr(prog, ';')) != NULL) {
12367         p++;
12368         while (*p == ' ') p++;
12369         prog = p;
12370     }
12371     if (*prog == '"' || *prog == '\'') {
12372         q = strchr(prog + 1, *prog);
12373     } else {
12374         q = strchr(prog, ' ');
12375     }
12376     if (q == NULL) q = prog + strlen(prog);
12377     p = q;
12378     while (p >= prog && *p != '/' && *p != '\\') p--;
12379     p++;
12380     if(p == prog && *p == '"') p++;
12381     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12382     memcpy(buf, p, q - p);
12383     buf[q - p] = NULLCHAR;
12384     if (!local) {
12385         strcat(buf, "@");
12386         strcat(buf, host);
12387     }
12388 }
12389
12390 char *
12391 TimeControlTagValue()
12392 {
12393     char buf[MSG_SIZ];
12394     if (!appData.clockMode) {
12395         strcpy(buf, "-");
12396     } else if (movesPerSession > 0) {
12397         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12398     } else if (timeIncrement == 0) {
12399         sprintf(buf, "%ld", timeControl/1000);
12400     } else {
12401         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12402     }
12403     return StrSave(buf);
12404 }
12405
12406 void
12407 SetGameInfo()
12408 {
12409     /* This routine is used only for certain modes */
12410     VariantClass v = gameInfo.variant;
12411     ChessMove r = GameUnfinished;
12412     char *p = NULL;
12413
12414     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12415         r = gameInfo.result; 
12416         p = gameInfo.resultDetails; 
12417         gameInfo.resultDetails = NULL;
12418     }
12419     ClearGameInfo(&gameInfo);
12420     gameInfo.variant = v;
12421
12422     switch (gameMode) {
12423       case MachinePlaysWhite:
12424         gameInfo.event = StrSave( appData.pgnEventHeader );
12425         gameInfo.site = StrSave(HostName());
12426         gameInfo.date = PGNDate();
12427         gameInfo.round = StrSave("-");
12428         gameInfo.white = StrSave(first.tidy);
12429         gameInfo.black = StrSave(UserName());
12430         gameInfo.timeControl = TimeControlTagValue();
12431         break;
12432
12433       case MachinePlaysBlack:
12434         gameInfo.event = StrSave( appData.pgnEventHeader );
12435         gameInfo.site = StrSave(HostName());
12436         gameInfo.date = PGNDate();
12437         gameInfo.round = StrSave("-");
12438         gameInfo.white = StrSave(UserName());
12439         gameInfo.black = StrSave(first.tidy);
12440         gameInfo.timeControl = TimeControlTagValue();
12441         break;
12442
12443       case TwoMachinesPlay:
12444         gameInfo.event = StrSave( appData.pgnEventHeader );
12445         gameInfo.site = StrSave(HostName());
12446         gameInfo.date = PGNDate();
12447         if (matchGame > 0) {
12448             char buf[MSG_SIZ];
12449             sprintf(buf, "%d", matchGame);
12450             gameInfo.round = StrSave(buf);
12451         } else {
12452             gameInfo.round = StrSave("-");
12453         }
12454         if (first.twoMachinesColor[0] == 'w') {
12455             gameInfo.white = StrSave(first.tidy);
12456             gameInfo.black = StrSave(second.tidy);
12457         } else {
12458             gameInfo.white = StrSave(second.tidy);
12459             gameInfo.black = StrSave(first.tidy);
12460         }
12461         gameInfo.timeControl = TimeControlTagValue();
12462         break;
12463
12464       case EditGame:
12465         gameInfo.event = StrSave("Edited game");
12466         gameInfo.site = StrSave(HostName());
12467         gameInfo.date = PGNDate();
12468         gameInfo.round = StrSave("-");
12469         gameInfo.white = StrSave("-");
12470         gameInfo.black = StrSave("-");
12471         gameInfo.result = r;
12472         gameInfo.resultDetails = p;
12473         break;
12474
12475       case EditPosition:
12476         gameInfo.event = StrSave("Edited position");
12477         gameInfo.site = StrSave(HostName());
12478         gameInfo.date = PGNDate();
12479         gameInfo.round = StrSave("-");
12480         gameInfo.white = StrSave("-");
12481         gameInfo.black = StrSave("-");
12482         break;
12483
12484       case IcsPlayingWhite:
12485       case IcsPlayingBlack:
12486       case IcsObserving:
12487       case IcsExamining:
12488         break;
12489
12490       case PlayFromGameFile:
12491         gameInfo.event = StrSave("Game from non-PGN file");
12492         gameInfo.site = StrSave(HostName());
12493         gameInfo.date = PGNDate();
12494         gameInfo.round = StrSave("-");
12495         gameInfo.white = StrSave("?");
12496         gameInfo.black = StrSave("?");
12497         break;
12498
12499       default:
12500         break;
12501     }
12502 }
12503
12504 void
12505 ReplaceComment(index, text)
12506      int index;
12507      char *text;
12508 {
12509     int len;
12510
12511     while (*text == '\n') text++;
12512     len = strlen(text);
12513     while (len > 0 && text[len - 1] == '\n') len--;
12514
12515     if (commentList[index] != NULL)
12516       free(commentList[index]);
12517
12518     if (len == 0) {
12519         commentList[index] = NULL;
12520         return;
12521     }
12522   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12523       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12524       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12525     commentList[index] = (char *) malloc(len + 2);
12526     strncpy(commentList[index], text, len);
12527     commentList[index][len] = '\n';
12528     commentList[index][len + 1] = NULLCHAR;
12529   } else { 
12530     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12531     char *p;
12532     commentList[index] = (char *) malloc(len + 6);
12533     strcpy(commentList[index], "{\n");
12534     strncpy(commentList[index]+2, text, len);
12535     commentList[index][len+2] = NULLCHAR;
12536     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12537     strcat(commentList[index], "\n}\n");
12538   }
12539 }
12540
12541 void
12542 CrushCRs(text)
12543      char *text;
12544 {
12545   char *p = text;
12546   char *q = text;
12547   char ch;
12548
12549   do {
12550     ch = *p++;
12551     if (ch == '\r') continue;
12552     *q++ = ch;
12553   } while (ch != '\0');
12554 }
12555
12556 void
12557 AppendComment(index, text, addBraces)
12558      int index;
12559      char *text;
12560      Boolean addBraces; // [HGM] braces: tells if we should add {}
12561 {
12562     int oldlen, len;
12563     char *old;
12564
12565 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12566     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12567
12568     CrushCRs(text);
12569     while (*text == '\n') text++;
12570     len = strlen(text);
12571     while (len > 0 && text[len - 1] == '\n') len--;
12572
12573     if (len == 0) return;
12574
12575     if (commentList[index] != NULL) {
12576         old = commentList[index];
12577         oldlen = strlen(old);
12578         while(commentList[index][oldlen-1] ==  '\n')
12579           commentList[index][--oldlen] = NULLCHAR;
12580         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12581         strcpy(commentList[index], old);
12582         free(old);
12583         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12584         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12585           if(addBraces) addBraces = FALSE; else { text++; len--; }
12586           while (*text == '\n') { text++; len--; }
12587           commentList[index][--oldlen] = NULLCHAR;
12588       }
12589         if(addBraces) strcat(commentList[index], "\n{\n");
12590         else          strcat(commentList[index], "\n");
12591         strcat(commentList[index], text);
12592         if(addBraces) strcat(commentList[index], "\n}\n");
12593         else          strcat(commentList[index], "\n");
12594     } else {
12595         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12596         if(addBraces)
12597              strcpy(commentList[index], "{\n");
12598         else commentList[index][0] = NULLCHAR;
12599         strcat(commentList[index], text);
12600         strcat(commentList[index], "\n");
12601         if(addBraces) strcat(commentList[index], "}\n");
12602     }
12603 }
12604
12605 static char * FindStr( char * text, char * sub_text )
12606 {
12607     char * result = strstr( text, sub_text );
12608
12609     if( result != NULL ) {
12610         result += strlen( sub_text );
12611     }
12612
12613     return result;
12614 }
12615
12616 /* [AS] Try to extract PV info from PGN comment */
12617 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12618 char *GetInfoFromComment( int index, char * text )
12619 {
12620     char * sep = text;
12621
12622     if( text != NULL && index > 0 ) {
12623         int score = 0;
12624         int depth = 0;
12625         int time = -1, sec = 0, deci;
12626         char * s_eval = FindStr( text, "[%eval " );
12627         char * s_emt = FindStr( text, "[%emt " );
12628
12629         if( s_eval != NULL || s_emt != NULL ) {
12630             /* New style */
12631             char delim;
12632
12633             if( s_eval != NULL ) {
12634                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12635                     return text;
12636                 }
12637
12638                 if( delim != ']' ) {
12639                     return text;
12640                 }
12641             }
12642
12643             if( s_emt != NULL ) {
12644             }
12645                 return text;
12646         }
12647         else {
12648             /* We expect something like: [+|-]nnn.nn/dd */
12649             int score_lo = 0;
12650
12651             if(*text != '{') return text; // [HGM] braces: must be normal comment
12652
12653             sep = strchr( text, '/' );
12654             if( sep == NULL || sep < (text+4) ) {
12655                 return text;
12656             }
12657
12658             time = -1; sec = -1; deci = -1;
12659             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12660                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12661                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12662                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12663                 return text;
12664             }
12665
12666             if( score_lo < 0 || score_lo >= 100 ) {
12667                 return text;
12668             }
12669
12670             if(sec >= 0) time = 600*time + 10*sec; else
12671             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12672
12673             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12674
12675             /* [HGM] PV time: now locate end of PV info */
12676             while( *++sep >= '0' && *sep <= '9'); // strip depth
12677             if(time >= 0)
12678             while( *++sep >= '0' && *sep <= '9'); // strip time
12679             if(sec >= 0)
12680             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12681             if(deci >= 0)
12682             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12683             while(*sep == ' ') sep++;
12684         }
12685
12686         if( depth <= 0 ) {
12687             return text;
12688         }
12689
12690         if( time < 0 ) {
12691             time = -1;
12692         }
12693
12694         pvInfoList[index-1].depth = depth;
12695         pvInfoList[index-1].score = score;
12696         pvInfoList[index-1].time  = 10*time; // centi-sec
12697         if(*sep == '}') *sep = 0; else *--sep = '{';
12698     }
12699     return sep;
12700 }
12701
12702 void
12703 SendToProgram(message, cps)
12704      char *message;
12705      ChessProgramState *cps;
12706 {
12707     int count, outCount, error;
12708     char buf[MSG_SIZ];
12709
12710     if (cps->pr == NULL) return;
12711     Attention(cps);
12712
12713     if (appData.debugMode) {
12714         TimeMark now;
12715         GetTimeMark(&now);
12716         fprintf(debugFP, "%ld >%-6s: %s",
12717                 SubtractTimeMarks(&now, &programStartTime),
12718                 cps->which, message);
12719     }
12720
12721     count = strlen(message);
12722     outCount = OutputToProcess(cps->pr, message, count, &error);
12723     if (outCount < count && !exiting
12724                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12725         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12726         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12727             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12728                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12729                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12730             } else {
12731                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12732             }
12733             gameInfo.resultDetails = StrSave(buf);
12734         }
12735         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12736     }
12737 }
12738
12739 void
12740 ReceiveFromProgram(isr, closure, message, count, error)
12741      InputSourceRef isr;
12742      VOIDSTAR closure;
12743      char *message;
12744      int count;
12745      int error;
12746 {
12747     char *end_str;
12748     char buf[MSG_SIZ];
12749     ChessProgramState *cps = (ChessProgramState *)closure;
12750
12751     if (isr != cps->isr) return; /* Killed intentionally */
12752     if (count <= 0) {
12753         if (count == 0) {
12754             sprintf(buf,
12755                     _("Error: %s chess program (%s) exited unexpectedly"),
12756                     cps->which, cps->program);
12757         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12758                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12759                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12760                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12761                 } else {
12762                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12763                 }
12764                 gameInfo.resultDetails = StrSave(buf);
12765             }
12766             RemoveInputSource(cps->isr);
12767             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12768         } else {
12769             sprintf(buf,
12770                     _("Error reading from %s chess program (%s)"),
12771                     cps->which, cps->program);
12772             RemoveInputSource(cps->isr);
12773
12774             /* [AS] Program is misbehaving badly... kill it */
12775             if( count == -2 ) {
12776                 DestroyChildProcess( cps->pr, 9 );
12777                 cps->pr = NoProc;
12778             }
12779
12780             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12781         }
12782         return;
12783     }
12784
12785     if ((end_str = strchr(message, '\r')) != NULL)
12786       *end_str = NULLCHAR;
12787     if ((end_str = strchr(message, '\n')) != NULL)
12788       *end_str = NULLCHAR;
12789
12790     if (appData.debugMode) {
12791         TimeMark now; int print = 1;
12792         char *quote = ""; char c; int i;
12793
12794         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12795                 char start = message[0];
12796                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12797                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12798                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12799                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12800                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12801                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12802                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12803                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12804                         { quote = "# "; print = (appData.engineComments == 2); }
12805                 message[0] = start; // restore original message
12806         }
12807         if(print) {
12808                 GetTimeMark(&now);
12809                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12810                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12811                         quote,
12812                         message);
12813         }
12814     }
12815
12816     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12817     if (appData.icsEngineAnalyze) {
12818         if (strstr(message, "whisper") != NULL ||
12819              strstr(message, "kibitz") != NULL ||
12820             strstr(message, "tellics") != NULL) return;
12821     }
12822
12823     HandleMachineMove(message, cps);
12824 }
12825
12826
12827 void
12828 SendTimeControl(cps, mps, tc, inc, sd, st)
12829      ChessProgramState *cps;
12830      int mps, inc, sd, st;
12831      long tc;
12832 {
12833     char buf[MSG_SIZ];
12834     int seconds;
12835
12836     if( timeControl_2 > 0 ) {
12837         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12838             tc = timeControl_2;
12839         }
12840     }
12841     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12842     inc /= cps->timeOdds;
12843     st  /= cps->timeOdds;
12844
12845     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12846
12847     if (st > 0) {
12848       /* Set exact time per move, normally using st command */
12849       if (cps->stKludge) {
12850         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12851         seconds = st % 60;
12852         if (seconds == 0) {
12853           sprintf(buf, "level 1 %d\n", st/60);
12854         } else {
12855           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12856         }
12857       } else {
12858         sprintf(buf, "st %d\n", st);
12859       }
12860     } else {
12861       /* Set conventional or incremental time control, using level command */
12862       if (seconds == 0) {
12863         /* Note old gnuchess bug -- minutes:seconds used to not work.
12864            Fixed in later versions, but still avoid :seconds
12865            when seconds is 0. */
12866         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12867       } else {
12868         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12869                 seconds, inc/1000);
12870       }
12871     }
12872     SendToProgram(buf, cps);
12873
12874     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12875     /* Orthogonally, limit search to given depth */
12876     if (sd > 0) {
12877       if (cps->sdKludge) {
12878         sprintf(buf, "depth\n%d\n", sd);
12879       } else {
12880         sprintf(buf, "sd %d\n", sd);
12881       }
12882       SendToProgram(buf, cps);
12883     }
12884
12885     if(cps->nps > 0) { /* [HGM] nps */
12886         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12887         else {
12888                 sprintf(buf, "nps %d\n", cps->nps);
12889               SendToProgram(buf, cps);
12890         }
12891     }
12892 }
12893
12894 ChessProgramState *WhitePlayer()
12895 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12896 {
12897     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12898        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12899         return &second;
12900     return &first;
12901 }
12902
12903 void
12904 SendTimeRemaining(cps, machineWhite)
12905      ChessProgramState *cps;
12906      int /*boolean*/ machineWhite;
12907 {
12908     char message[MSG_SIZ];
12909     long time, otime;
12910
12911     /* Note: this routine must be called when the clocks are stopped
12912        or when they have *just* been set or switched; otherwise
12913        it will be off by the time since the current tick started.
12914     */
12915     if (machineWhite) {
12916         time = whiteTimeRemaining / 10;
12917         otime = blackTimeRemaining / 10;
12918     } else {
12919         time = blackTimeRemaining / 10;
12920         otime = whiteTimeRemaining / 10;
12921     }
12922     /* [HGM] translate opponent's time by time-odds factor */
12923     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12924     if (appData.debugMode) {
12925         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12926     }
12927
12928     if (time <= 0) time = 1;
12929     if (otime <= 0) otime = 1;
12930
12931     sprintf(message, "time %ld\n", time);
12932     SendToProgram(message, cps);
12933
12934     sprintf(message, "otim %ld\n", otime);
12935     SendToProgram(message, cps);
12936 }
12937
12938 int
12939 BoolFeature(p, name, loc, cps)
12940      char **p;
12941      char *name;
12942      int *loc;
12943      ChessProgramState *cps;
12944 {
12945   char buf[MSG_SIZ];
12946   int len = strlen(name);
12947   int val;
12948   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12949     (*p) += len + 1;
12950     sscanf(*p, "%d", &val);
12951     *loc = (val != 0);
12952     while (**p && **p != ' ') (*p)++;
12953     sprintf(buf, "accepted %s\n", name);
12954     SendToProgram(buf, cps);
12955     return TRUE;
12956   }
12957   return FALSE;
12958 }
12959
12960 int
12961 IntFeature(p, name, loc, cps)
12962      char **p;
12963      char *name;
12964      int *loc;
12965      ChessProgramState *cps;
12966 {
12967   char buf[MSG_SIZ];
12968   int len = strlen(name);
12969   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12970     (*p) += len + 1;
12971     sscanf(*p, "%d", loc);
12972     while (**p && **p != ' ') (*p)++;
12973     sprintf(buf, "accepted %s\n", name);
12974     SendToProgram(buf, cps);
12975     return TRUE;
12976   }
12977   return FALSE;
12978 }
12979
12980 int
12981 StringFeature(p, name, loc, cps)
12982      char **p;
12983      char *name;
12984      char loc[];
12985      ChessProgramState *cps;
12986 {
12987   char buf[MSG_SIZ];
12988   int len = strlen(name);
12989   if (strncmp((*p), name, len) == 0
12990       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12991     (*p) += len + 2;
12992     sscanf(*p, "%[^\"]", loc);
12993     while (**p && **p != '\"') (*p)++;
12994     if (**p == '\"') (*p)++;
12995     sprintf(buf, "accepted %s\n", name);
12996     SendToProgram(buf, cps);
12997     return TRUE;
12998   }
12999   return FALSE;
13000 }
13001
13002 int
13003 ParseOption(Option *opt, ChessProgramState *cps)
13004 // [HGM] options: process the string that defines an engine option, and determine
13005 // name, type, default value, and allowed value range
13006 {
13007         char *p, *q, buf[MSG_SIZ];
13008         int n, min = (-1)<<31, max = 1<<31, def;
13009
13010         if(p = strstr(opt->name, " -spin ")) {
13011             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13012             if(max < min) max = min; // enforce consistency
13013             if(def < min) def = min;
13014             if(def > max) def = max;
13015             opt->value = def;
13016             opt->min = min;
13017             opt->max = max;
13018             opt->type = Spin;
13019         } else if((p = strstr(opt->name, " -slider "))) {
13020             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13021             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13022             if(max < min) max = min; // enforce consistency
13023             if(def < min) def = min;
13024             if(def > max) def = max;
13025             opt->value = def;
13026             opt->min = min;
13027             opt->max = max;
13028             opt->type = Spin; // Slider;
13029         } else if((p = strstr(opt->name, " -string "))) {
13030             opt->textValue = p+9;
13031             opt->type = TextBox;
13032         } else if((p = strstr(opt->name, " -file "))) {
13033             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13034             opt->textValue = p+7;
13035             opt->type = TextBox; // FileName;
13036         } else if((p = strstr(opt->name, " -path "))) {
13037             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13038             opt->textValue = p+7;
13039             opt->type = TextBox; // PathName;
13040         } else if(p = strstr(opt->name, " -check ")) {
13041             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13042             opt->value = (def != 0);
13043             opt->type = CheckBox;
13044         } else if(p = strstr(opt->name, " -combo ")) {
13045             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13046             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13047             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13048             opt->value = n = 0;
13049             while(q = StrStr(q, " /// ")) {
13050                 n++; *q = 0;    // count choices, and null-terminate each of them
13051                 q += 5;
13052                 if(*q == '*') { // remember default, which is marked with * prefix
13053                     q++;
13054                     opt->value = n;
13055                 }
13056                 cps->comboList[cps->comboCnt++] = q;
13057             }
13058             cps->comboList[cps->comboCnt++] = NULL;
13059             opt->max = n + 1;
13060             opt->type = ComboBox;
13061         } else if(p = strstr(opt->name, " -button")) {
13062             opt->type = Button;
13063         } else if(p = strstr(opt->name, " -save")) {
13064             opt->type = SaveButton;
13065         } else return FALSE;
13066         *p = 0; // terminate option name
13067         // now look if the command-line options define a setting for this engine option.
13068         if(cps->optionSettings && cps->optionSettings[0])
13069             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13070         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13071                 sprintf(buf, "option %s", p);
13072                 if(p = strstr(buf, ",")) *p = 0;
13073                 strcat(buf, "\n");
13074                 SendToProgram(buf, cps);
13075         }
13076         return TRUE;
13077 }
13078
13079 void
13080 FeatureDone(cps, val)
13081      ChessProgramState* cps;
13082      int val;
13083 {
13084   DelayedEventCallback cb = GetDelayedEvent();
13085   if ((cb == InitBackEnd3 && cps == &first) ||
13086       (cb == TwoMachinesEventIfReady && cps == &second)) {
13087     CancelDelayedEvent();
13088     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13089   }
13090   cps->initDone = val;
13091 }
13092
13093 /* Parse feature command from engine */
13094 void
13095 ParseFeatures(args, cps)
13096      char* args;
13097      ChessProgramState *cps;
13098 {
13099   char *p = args;
13100   char *q;
13101   int val;
13102   char buf[MSG_SIZ];
13103
13104   for (;;) {
13105     while (*p == ' ') p++;
13106     if (*p == NULLCHAR) return;
13107
13108     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13109     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13110     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13111     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13112     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13113     if (BoolFeature(&p, "reuse", &val, cps)) {
13114       /* Engine can disable reuse, but can't enable it if user said no */
13115       if (!val) cps->reuse = FALSE;
13116       continue;
13117     }
13118     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13119     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13120       if (gameMode == TwoMachinesPlay) {
13121         DisplayTwoMachinesTitle();
13122       } else {
13123         DisplayTitle("");
13124       }
13125       continue;
13126     }
13127     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13128     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13129     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13130     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13131     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13132     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13133     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13134     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13135     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13136     if (IntFeature(&p, "done", &val, cps)) {
13137       FeatureDone(cps, val);
13138       continue;
13139     }
13140     /* Added by Tord: */
13141     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13142     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13143     /* End of additions by Tord */
13144
13145     /* [HGM] added features: */
13146     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13147     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13148     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13149     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13150     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13151     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13152     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13153         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13154             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13155             SendToProgram(buf, cps);
13156             continue;
13157         }
13158         if(cps->nrOptions >= MAX_OPTIONS) {
13159             cps->nrOptions--;
13160             sprintf(buf, "%s engine has too many options\n", cps->which);
13161             DisplayError(buf, 0);
13162         }
13163         continue;
13164     }
13165     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13166     /* End of additions by HGM */
13167
13168     /* unknown feature: complain and skip */
13169     q = p;
13170     while (*q && *q != '=') q++;
13171     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13172     SendToProgram(buf, cps);
13173     p = q;
13174     if (*p == '=') {
13175       p++;
13176       if (*p == '\"') {
13177         p++;
13178         while (*p && *p != '\"') p++;
13179         if (*p == '\"') p++;
13180       } else {
13181         while (*p && *p != ' ') p++;
13182       }
13183     }
13184   }
13185
13186 }
13187
13188 void
13189 PeriodicUpdatesEvent(newState)
13190      int newState;
13191 {
13192     if (newState == appData.periodicUpdates)
13193       return;
13194
13195     appData.periodicUpdates=newState;
13196
13197     /* Display type changes, so update it now */
13198 //    DisplayAnalysis();
13199
13200     /* Get the ball rolling again... */
13201     if (newState) {
13202         AnalysisPeriodicEvent(1);
13203         StartAnalysisClock();
13204     }
13205 }
13206
13207 void
13208 PonderNextMoveEvent(newState)
13209      int newState;
13210 {
13211     if (newState == appData.ponderNextMove) return;
13212     if (gameMode == EditPosition) EditPositionDone(TRUE);
13213     if (newState) {
13214         SendToProgram("hard\n", &first);
13215         if (gameMode == TwoMachinesPlay) {
13216             SendToProgram("hard\n", &second);
13217         }
13218     } else {
13219         SendToProgram("easy\n", &first);
13220         thinkOutput[0] = NULLCHAR;
13221         if (gameMode == TwoMachinesPlay) {
13222             SendToProgram("easy\n", &second);
13223         }
13224     }
13225     appData.ponderNextMove = newState;
13226 }
13227
13228 void
13229 NewSettingEvent(option, command, value)
13230      char *command;
13231      int option, value;
13232 {
13233     char buf[MSG_SIZ];
13234
13235     if (gameMode == EditPosition) EditPositionDone(TRUE);
13236     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13237     SendToProgram(buf, &first);
13238     if (gameMode == TwoMachinesPlay) {
13239         SendToProgram(buf, &second);
13240     }
13241 }
13242
13243 void
13244 ShowThinkingEvent()
13245 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13246 {
13247     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13248     int newState = appData.showThinking
13249         // [HGM] thinking: other features now need thinking output as well
13250         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13251
13252     if (oldState == newState) return;
13253     oldState = newState;
13254     if (gameMode == EditPosition) EditPositionDone(TRUE);
13255     if (oldState) {
13256         SendToProgram("post\n", &first);
13257         if (gameMode == TwoMachinesPlay) {
13258             SendToProgram("post\n", &second);
13259         }
13260     } else {
13261         SendToProgram("nopost\n", &first);
13262         thinkOutput[0] = NULLCHAR;
13263         if (gameMode == TwoMachinesPlay) {
13264             SendToProgram("nopost\n", &second);
13265         }
13266     }
13267 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13268 }
13269
13270 void
13271 AskQuestionEvent(title, question, replyPrefix, which)
13272      char *title; char *question; char *replyPrefix; char *which;
13273 {
13274   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13275   if (pr == NoProc) return;
13276   AskQuestion(title, question, replyPrefix, pr);
13277 }
13278
13279 void
13280 DisplayMove(moveNumber)
13281      int moveNumber;
13282 {
13283     char message[MSG_SIZ];
13284     char res[MSG_SIZ];
13285     char cpThinkOutput[MSG_SIZ];
13286
13287     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13288
13289     if (moveNumber == forwardMostMove - 1 ||
13290         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13291
13292         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13293
13294         if (strchr(cpThinkOutput, '\n')) {
13295             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13296         }
13297     } else {
13298         *cpThinkOutput = NULLCHAR;
13299     }
13300
13301     /* [AS] Hide thinking from human user */
13302     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13303         *cpThinkOutput = NULLCHAR;
13304         if( thinkOutput[0] != NULLCHAR ) {
13305             int i;
13306
13307             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13308                 cpThinkOutput[i] = '.';
13309             }
13310             cpThinkOutput[i] = NULLCHAR;
13311             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13312         }
13313     }
13314
13315     if (moveNumber == forwardMostMove - 1 &&
13316         gameInfo.resultDetails != NULL) {
13317         if (gameInfo.resultDetails[0] == NULLCHAR) {
13318             sprintf(res, " %s", PGNResult(gameInfo.result));
13319         } else {
13320             sprintf(res, " {%s} %s",
13321                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13322         }
13323     } else {
13324         res[0] = NULLCHAR;
13325     }
13326
13327     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13328         DisplayMessage(res, cpThinkOutput);
13329     } else {
13330         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13331                 WhiteOnMove(moveNumber) ? " " : ".. ",
13332                 parseList[moveNumber], res);
13333         DisplayMessage(message, cpThinkOutput);
13334     }
13335 }
13336
13337 void
13338 DisplayComment(moveNumber, text)
13339      int moveNumber;
13340      char *text;
13341 {
13342     char title[MSG_SIZ];
13343     char buf[8000]; // comment can be long!
13344     int score, depth;
13345     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13346       strcpy(title, "Comment");
13347     } else {
13348       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13349               WhiteOnMove(moveNumber) ? " " : ".. ",
13350               parseList[moveNumber]);
13351     }
13352     // [HGM] PV info: display PV info together with (or as) comment
13353     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13354       if(text == NULL) text = "";                                           
13355       score = pvInfoList[moveNumber].score;
13356       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13357               depth, (pvInfoList[moveNumber].time+50)/100, text);
13358       text = buf;
13359     }
13360     if (text != NULL && (appData.autoDisplayComment || commentUp))
13361       CommentPopUp(title, text);
13362 }
13363
13364 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13365  * might be busy thinking or pondering.  It can be omitted if your
13366  * gnuchess is configured to stop thinking immediately on any user
13367  * input.  However, that gnuchess feature depends on the FIONREAD
13368  * ioctl, which does not work properly on some flavors of Unix.
13369  */
13370 void
13371 Attention(cps)
13372      ChessProgramState *cps;
13373 {
13374 #if ATTENTION
13375     if (!cps->useSigint) return;
13376     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13377     switch (gameMode) {
13378       case MachinePlaysWhite:
13379       case MachinePlaysBlack:
13380       case TwoMachinesPlay:
13381       case IcsPlayingWhite:
13382       case IcsPlayingBlack:
13383       case AnalyzeMode:
13384       case AnalyzeFile:
13385         /* Skip if we know it isn't thinking */
13386         if (!cps->maybeThinking) return;
13387         if (appData.debugMode)
13388           fprintf(debugFP, "Interrupting %s\n", cps->which);
13389         InterruptChildProcess(cps->pr);
13390         cps->maybeThinking = FALSE;
13391         break;
13392       default:
13393         break;
13394     }
13395 #endif /*ATTENTION*/
13396 }
13397
13398 int
13399 CheckFlags()
13400 {
13401     if (whiteTimeRemaining <= 0) {
13402         if (!whiteFlag) {
13403             whiteFlag = TRUE;
13404             if (appData.icsActive) {
13405                 if (appData.autoCallFlag &&
13406                     gameMode == IcsPlayingBlack && !blackFlag) {
13407                   SendToICS(ics_prefix);
13408                   SendToICS("flag\n");
13409                 }
13410             } else {
13411                 if (blackFlag) {
13412                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13413                 } else {
13414                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13415                     if (appData.autoCallFlag) {
13416                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13417                         return TRUE;
13418                     }
13419                 }
13420             }
13421         }
13422     }
13423     if (blackTimeRemaining <= 0) {
13424         if (!blackFlag) {
13425             blackFlag = TRUE;
13426             if (appData.icsActive) {
13427                 if (appData.autoCallFlag &&
13428                     gameMode == IcsPlayingWhite && !whiteFlag) {
13429                   SendToICS(ics_prefix);
13430                   SendToICS("flag\n");
13431                 }
13432             } else {
13433                 if (whiteFlag) {
13434                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13435                 } else {
13436                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13437                     if (appData.autoCallFlag) {
13438                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13439                         return TRUE;
13440                     }
13441                 }
13442             }
13443         }
13444     }
13445     return FALSE;
13446 }
13447
13448 void
13449 CheckTimeControl()
13450 {
13451     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13452         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13453
13454     /*
13455      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13456      */
13457     if ( !WhiteOnMove(forwardMostMove) )
13458         /* White made time control */
13459         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13460         /* [HGM] time odds: correct new time quota for time odds! */
13461                                             / WhitePlayer()->timeOdds;
13462       else
13463         /* Black made time control */
13464         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13465                                             / WhitePlayer()->other->timeOdds;
13466 }
13467
13468 void
13469 DisplayBothClocks()
13470 {
13471     int wom = gameMode == EditPosition ?
13472       !blackPlaysFirst : WhiteOnMove(currentMove);
13473     DisplayWhiteClock(whiteTimeRemaining, wom);
13474     DisplayBlackClock(blackTimeRemaining, !wom);
13475 }
13476
13477
13478 /* Timekeeping seems to be a portability nightmare.  I think everyone
13479    has ftime(), but I'm really not sure, so I'm including some ifdefs
13480    to use other calls if you don't.  Clocks will be less accurate if
13481    you have neither ftime nor gettimeofday.
13482 */
13483
13484 /* VS 2008 requires the #include outside of the function */
13485 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13486 #include <sys/timeb.h>
13487 #endif
13488
13489 /* Get the current time as a TimeMark */
13490 void
13491 GetTimeMark(tm)
13492      TimeMark *tm;
13493 {
13494 #if HAVE_GETTIMEOFDAY
13495
13496     struct timeval timeVal;
13497     struct timezone timeZone;
13498
13499     gettimeofday(&timeVal, &timeZone);
13500     tm->sec = (long) timeVal.tv_sec;
13501     tm->ms = (int) (timeVal.tv_usec / 1000L);
13502
13503 #else /*!HAVE_GETTIMEOFDAY*/
13504 #if HAVE_FTIME
13505
13506 // include <sys/timeb.h> / moved to just above start of function
13507     struct timeb timeB;
13508
13509     ftime(&timeB);
13510     tm->sec = (long) timeB.time;
13511     tm->ms = (int) timeB.millitm;
13512
13513 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13514     tm->sec = (long) time(NULL);
13515     tm->ms = 0;
13516 #endif
13517 #endif
13518 }
13519
13520 /* Return the difference in milliseconds between two
13521    time marks.  We assume the difference will fit in a long!
13522 */
13523 long
13524 SubtractTimeMarks(tm2, tm1)
13525      TimeMark *tm2, *tm1;
13526 {
13527     return 1000L*(tm2->sec - tm1->sec) +
13528            (long) (tm2->ms - tm1->ms);
13529 }
13530
13531
13532 /*
13533  * Code to manage the game clocks.
13534  *
13535  * In tournament play, black starts the clock and then white makes a move.
13536  * We give the human user a slight advantage if he is playing white---the
13537  * clocks don't run until he makes his first move, so it takes zero time.
13538  * Also, we don't account for network lag, so we could get out of sync
13539  * with GNU Chess's clock -- but then, referees are always right.
13540  */
13541
13542 static TimeMark tickStartTM;
13543 static long intendedTickLength;
13544
13545 long
13546 NextTickLength(timeRemaining)
13547      long timeRemaining;
13548 {
13549     long nominalTickLength, nextTickLength;
13550
13551     if (timeRemaining > 0L && timeRemaining <= 10000L)
13552       nominalTickLength = 100L;
13553     else
13554       nominalTickLength = 1000L;
13555     nextTickLength = timeRemaining % nominalTickLength;
13556     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13557
13558     return nextTickLength;
13559 }
13560
13561 /* Adjust clock one minute up or down */
13562 void
13563 AdjustClock(Boolean which, int dir)
13564 {
13565     if(which) blackTimeRemaining += 60000*dir;
13566     else      whiteTimeRemaining += 60000*dir;
13567     DisplayBothClocks();
13568 }
13569
13570 /* Stop clocks and reset to a fresh time control */
13571 void
13572 ResetClocks()
13573 {
13574     (void) StopClockTimer();
13575     if (appData.icsActive) {
13576         whiteTimeRemaining = blackTimeRemaining = 0;
13577     } else if (searchTime) {
13578         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13579         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13580     } else { /* [HGM] correct new time quote for time odds */
13581         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13582         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13583     }
13584     if (whiteFlag || blackFlag) {
13585         DisplayTitle("");
13586         whiteFlag = blackFlag = FALSE;
13587     }
13588     DisplayBothClocks();
13589 }
13590
13591 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13592
13593 /* Decrement running clock by amount of time that has passed */
13594 void
13595 DecrementClocks()
13596 {
13597     long timeRemaining;
13598     long lastTickLength, fudge;
13599     TimeMark now;
13600
13601     if (!appData.clockMode) return;
13602     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13603
13604     GetTimeMark(&now);
13605
13606     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13607
13608     /* Fudge if we woke up a little too soon */
13609     fudge = intendedTickLength - lastTickLength;
13610     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13611
13612     if (WhiteOnMove(forwardMostMove)) {
13613         if(whiteNPS >= 0) lastTickLength = 0;
13614         timeRemaining = whiteTimeRemaining -= lastTickLength;
13615         DisplayWhiteClock(whiteTimeRemaining - fudge,
13616                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13617     } else {
13618         if(blackNPS >= 0) lastTickLength = 0;
13619         timeRemaining = blackTimeRemaining -= lastTickLength;
13620         DisplayBlackClock(blackTimeRemaining - fudge,
13621                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13622     }
13623
13624     if (CheckFlags()) return;
13625
13626     tickStartTM = now;
13627     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13628     StartClockTimer(intendedTickLength);
13629
13630     /* if the time remaining has fallen below the alarm threshold, sound the
13631      * alarm. if the alarm has sounded and (due to a takeback or time control
13632      * with increment) the time remaining has increased to a level above the
13633      * threshold, reset the alarm so it can sound again.
13634      */
13635
13636     if (appData.icsActive && appData.icsAlarm) {
13637
13638         /* make sure we are dealing with the user's clock */
13639         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13640                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13641            )) return;
13642
13643         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13644             alarmSounded = FALSE;
13645         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13646             PlayAlarmSound();
13647             alarmSounded = TRUE;
13648         }
13649     }
13650 }
13651
13652
13653 /* A player has just moved, so stop the previously running
13654    clock and (if in clock mode) start the other one.
13655    We redisplay both clocks in case we're in ICS mode, because
13656    ICS gives us an update to both clocks after every move.
13657    Note that this routine is called *after* forwardMostMove
13658    is updated, so the last fractional tick must be subtracted
13659    from the color that is *not* on move now.
13660 */
13661 void
13662 SwitchClocks()
13663 {
13664     long lastTickLength;
13665     TimeMark now;
13666     int flagged = FALSE;
13667
13668     GetTimeMark(&now);
13669
13670     if (StopClockTimer() && appData.clockMode) {
13671         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13672         if (WhiteOnMove(forwardMostMove)) {
13673             if(blackNPS >= 0) lastTickLength = 0;
13674             blackTimeRemaining -= lastTickLength;
13675            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13676 //         if(pvInfoList[forwardMostMove-1].time == -1)
13677                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13678                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13679         } else {
13680            if(whiteNPS >= 0) lastTickLength = 0;
13681            whiteTimeRemaining -= lastTickLength;
13682            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13683 //         if(pvInfoList[forwardMostMove-1].time == -1)
13684                  pvInfoList[forwardMostMove-1].time =
13685                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13686         }
13687         flagged = CheckFlags();
13688     }
13689     CheckTimeControl();
13690
13691     if (flagged || !appData.clockMode) return;
13692
13693     switch (gameMode) {
13694       case MachinePlaysBlack:
13695       case MachinePlaysWhite:
13696       case BeginningOfGame:
13697         if (pausing) return;
13698         break;
13699
13700       case EditGame:
13701       case PlayFromGameFile:
13702       case IcsExamining:
13703         return;
13704
13705       default:
13706         break;
13707     }
13708
13709     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13710         if(WhiteOnMove(forwardMostMove))
13711              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13712         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13713     }
13714
13715     tickStartTM = now;
13716     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13717       whiteTimeRemaining : blackTimeRemaining);
13718     StartClockTimer(intendedTickLength);
13719 }
13720
13721
13722 /* Stop both clocks */
13723 void
13724 StopClocks()
13725 {
13726     long lastTickLength;
13727     TimeMark now;
13728
13729     if (!StopClockTimer()) return;
13730     if (!appData.clockMode) return;
13731
13732     GetTimeMark(&now);
13733
13734     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13735     if (WhiteOnMove(forwardMostMove)) {
13736         if(whiteNPS >= 0) lastTickLength = 0;
13737         whiteTimeRemaining -= lastTickLength;
13738         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13739     } else {
13740         if(blackNPS >= 0) lastTickLength = 0;
13741         blackTimeRemaining -= lastTickLength;
13742         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13743     }
13744     CheckFlags();
13745 }
13746
13747 /* Start clock of player on move.  Time may have been reset, so
13748    if clock is already running, stop and restart it. */
13749 void
13750 StartClocks()
13751 {
13752     (void) StopClockTimer(); /* in case it was running already */
13753     DisplayBothClocks();
13754     if (CheckFlags()) return;
13755
13756     if (!appData.clockMode) return;
13757     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13758
13759     GetTimeMark(&tickStartTM);
13760     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13761       whiteTimeRemaining : blackTimeRemaining);
13762
13763    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13764     whiteNPS = blackNPS = -1;
13765     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13766        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13767         whiteNPS = first.nps;
13768     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13769        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13770         blackNPS = first.nps;
13771     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13772         whiteNPS = second.nps;
13773     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13774         blackNPS = second.nps;
13775     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13776
13777     StartClockTimer(intendedTickLength);
13778 }
13779
13780 char *
13781 TimeString(ms)
13782      long ms;
13783 {
13784     long second, minute, hour, day;
13785     char *sign = "";
13786     static char buf[32];
13787
13788     if (ms > 0 && ms <= 9900) {
13789       /* convert milliseconds to tenths, rounding up */
13790       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13791
13792       sprintf(buf, " %03.1f ", tenths/10.0);
13793       return buf;
13794     }
13795
13796     /* convert milliseconds to seconds, rounding up */
13797     /* use floating point to avoid strangeness of integer division
13798        with negative dividends on many machines */
13799     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13800
13801     if (second < 0) {
13802         sign = "-";
13803         second = -second;
13804     }
13805
13806     day = second / (60 * 60 * 24);
13807     second = second % (60 * 60 * 24);
13808     hour = second / (60 * 60);
13809     second = second % (60 * 60);
13810     minute = second / 60;
13811     second = second % 60;
13812
13813     if (day > 0)
13814       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13815               sign, day, hour, minute, second);
13816     else if (hour > 0)
13817       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13818     else
13819       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13820
13821     return buf;
13822 }
13823
13824
13825 /*
13826  * This is necessary because some C libraries aren't ANSI C compliant yet.
13827  */
13828 char *
13829 StrStr(string, match)
13830      char *string, *match;
13831 {
13832     int i, length;
13833
13834     length = strlen(match);
13835
13836     for (i = strlen(string) - length; i >= 0; i--, string++)
13837       if (!strncmp(match, string, length))
13838         return string;
13839
13840     return NULL;
13841 }
13842
13843 char *
13844 StrCaseStr(string, match)
13845      char *string, *match;
13846 {
13847     int i, j, length;
13848
13849     length = strlen(match);
13850
13851     for (i = strlen(string) - length; i >= 0; i--, string++) {
13852         for (j = 0; j < length; j++) {
13853             if (ToLower(match[j]) != ToLower(string[j]))
13854               break;
13855         }
13856         if (j == length) return string;
13857     }
13858
13859     return NULL;
13860 }
13861
13862 #ifndef _amigados
13863 int
13864 StrCaseCmp(s1, s2)
13865      char *s1, *s2;
13866 {
13867     char c1, c2;
13868
13869     for (;;) {
13870         c1 = ToLower(*s1++);
13871         c2 = ToLower(*s2++);
13872         if (c1 > c2) return 1;
13873         if (c1 < c2) return -1;
13874         if (c1 == NULLCHAR) return 0;
13875     }
13876 }
13877
13878
13879 int
13880 ToLower(c)
13881      int c;
13882 {
13883     return isupper(c) ? tolower(c) : c;
13884 }
13885
13886
13887 int
13888 ToUpper(c)
13889      int c;
13890 {
13891     return islower(c) ? toupper(c) : c;
13892 }
13893 #endif /* !_amigados    */
13894
13895 char *
13896 StrSave(s)
13897      char *s;
13898 {
13899     char *ret;
13900
13901     if ((ret = (char *) malloc(strlen(s) + 1))) {
13902         strcpy(ret, s);
13903     }
13904     return ret;
13905 }
13906
13907 char *
13908 StrSavePtr(s, savePtr)
13909      char *s, **savePtr;
13910 {
13911     if (*savePtr) {
13912         free(*savePtr);
13913     }
13914     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13915         strcpy(*savePtr, s);
13916     }
13917     return(*savePtr);
13918 }
13919
13920 char *
13921 PGNDate()
13922 {
13923     time_t clock;
13924     struct tm *tm;
13925     char buf[MSG_SIZ];
13926
13927     clock = time((time_t *)NULL);
13928     tm = localtime(&clock);
13929     sprintf(buf, "%04d.%02d.%02d",
13930             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13931     return StrSave(buf);
13932 }
13933
13934
13935 char *
13936 PositionToFEN(move, overrideCastling)
13937      int move;
13938      char *overrideCastling;
13939 {
13940     int i, j, fromX, fromY, toX, toY;
13941     int whiteToPlay;
13942     char buf[128];
13943     char *p, *q;
13944     int emptycount;
13945     ChessSquare piece;
13946
13947     whiteToPlay = (gameMode == EditPosition) ?
13948       !blackPlaysFirst : (move % 2 == 0);
13949     p = buf;
13950
13951     /* Piece placement data */
13952     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13953         emptycount = 0;
13954         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13955             if (boards[move][i][j] == EmptySquare) {
13956                 emptycount++;
13957             } else { ChessSquare piece = boards[move][i][j];
13958                 if (emptycount > 0) {
13959                     if(emptycount<10) /* [HGM] can be >= 10 */
13960                         *p++ = '0' + emptycount;
13961                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13962                     emptycount = 0;
13963                 }
13964                 if(PieceToChar(piece) == '+') {
13965                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13966                     *p++ = '+';
13967                     piece = (ChessSquare)(DEMOTED piece);
13968                 }
13969                 *p++ = PieceToChar(piece);
13970                 if(p[-1] == '~') {
13971                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13972                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13973                     *p++ = '~';
13974                 }
13975             }
13976         }
13977         if (emptycount > 0) {
13978             if(emptycount<10) /* [HGM] can be >= 10 */
13979                 *p++ = '0' + emptycount;
13980             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13981             emptycount = 0;
13982         }
13983         *p++ = '/';
13984     }
13985     *(p - 1) = ' ';
13986
13987     /* [HGM] print Crazyhouse or Shogi holdings */
13988     if( gameInfo.holdingsWidth ) {
13989         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13990         q = p;
13991         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13992             piece = boards[move][i][BOARD_WIDTH-1];
13993             if( piece != EmptySquare )
13994               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13995                   *p++ = PieceToChar(piece);
13996         }
13997         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13998             piece = boards[move][BOARD_HEIGHT-i-1][0];
13999             if( piece != EmptySquare )
14000               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14001                   *p++ = PieceToChar(piece);
14002         }
14003
14004         if( q == p ) *p++ = '-';
14005         *p++ = ']';
14006         *p++ = ' ';
14007     }
14008
14009     /* Active color */
14010     *p++ = whiteToPlay ? 'w' : 'b';
14011     *p++ = ' ';
14012
14013   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14014     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14015   } else {
14016   if(nrCastlingRights) {
14017      q = p;
14018      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14019        /* [HGM] write directly from rights */
14020            if(boards[move][CASTLING][2] != NoRights &&
14021               boards[move][CASTLING][0] != NoRights   )
14022                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14023            if(boards[move][CASTLING][2] != NoRights &&
14024               boards[move][CASTLING][1] != NoRights   )
14025                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14026            if(boards[move][CASTLING][5] != NoRights &&
14027               boards[move][CASTLING][3] != NoRights   )
14028                 *p++ = boards[move][CASTLING][3] + AAA;
14029            if(boards[move][CASTLING][5] != NoRights &&
14030               boards[move][CASTLING][4] != NoRights   )
14031                 *p++ = boards[move][CASTLING][4] + AAA;
14032      } else {
14033
14034         /* [HGM] write true castling rights */
14035         if( nrCastlingRights == 6 ) {
14036             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14037                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14038             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14039                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14040             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14041                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14042             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14043                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14044         }
14045      }
14046      if (q == p) *p++ = '-'; /* No castling rights */
14047      *p++ = ' ';
14048   }
14049
14050   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14051      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14052     /* En passant target square */
14053     if (move > backwardMostMove) {
14054         fromX = moveList[move - 1][0] - AAA;
14055         fromY = moveList[move - 1][1] - ONE;
14056         toX = moveList[move - 1][2] - AAA;
14057         toY = moveList[move - 1][3] - ONE;
14058         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14059             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14060             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14061             fromX == toX) {
14062             /* 2-square pawn move just happened */
14063             *p++ = toX + AAA;
14064             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14065         } else {
14066             *p++ = '-';
14067         }
14068     } else if(move == backwardMostMove) {
14069         // [HGM] perhaps we should always do it like this, and forget the above?
14070         if((signed char)boards[move][EP_STATUS] >= 0) {
14071             *p++ = boards[move][EP_STATUS] + AAA;
14072             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14073         } else {
14074             *p++ = '-';
14075         }
14076     } else {
14077         *p++ = '-';
14078     }
14079     *p++ = ' ';
14080   }
14081   }
14082
14083     /* [HGM] find reversible plies */
14084     {   int i = 0, j=move;
14085
14086         if (appData.debugMode) { int k;
14087             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14088             for(k=backwardMostMove; k<=forwardMostMove; k++)
14089                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14090
14091         }
14092
14093         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14094         if( j == backwardMostMove ) i += initialRulePlies;
14095         sprintf(p, "%d ", i);
14096         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14097     }
14098     /* Fullmove number */
14099     sprintf(p, "%d", (move / 2) + 1);
14100
14101     return StrSave(buf);
14102 }
14103
14104 Boolean
14105 ParseFEN(board, blackPlaysFirst, fen)
14106     Board board;
14107      int *blackPlaysFirst;
14108      char *fen;
14109 {
14110     int i, j;
14111     char *p;
14112     int emptycount;
14113     ChessSquare piece;
14114
14115     p = fen;
14116
14117     /* [HGM] by default clear Crazyhouse holdings, if present */
14118     if(gameInfo.holdingsWidth) {
14119        for(i=0; i<BOARD_HEIGHT; i++) {
14120            board[i][0]             = EmptySquare; /* black holdings */
14121            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14122            board[i][1]             = (ChessSquare) 0; /* black counts */
14123            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14124        }
14125     }
14126
14127     /* Piece placement data */
14128     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14129         j = 0;
14130         for (;;) {
14131             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14132                 if (*p == '/') p++;
14133                 emptycount = gameInfo.boardWidth - j;
14134                 while (emptycount--)
14135                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14136                 break;
14137 #if(BOARD_FILES >= 10)
14138             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14139                 p++; emptycount=10;
14140                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14141                 while (emptycount--)
14142                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14143 #endif
14144             } else if (isdigit(*p)) {
14145                 emptycount = *p++ - '0';
14146                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14147                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14148                 while (emptycount--)
14149                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14150             } else if (*p == '+' || isalpha(*p)) {
14151                 if (j >= gameInfo.boardWidth) return FALSE;
14152                 if(*p=='+') {
14153                     piece = CharToPiece(*++p);
14154                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14155                     piece = (ChessSquare) (PROMOTED piece ); p++;
14156                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14157                 } else piece = CharToPiece(*p++);
14158
14159                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14160                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14161                     piece = (ChessSquare) (PROMOTED piece);
14162                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14163                     p++;
14164                 }
14165                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14166             } else {
14167                 return FALSE;
14168             }
14169         }
14170     }
14171     while (*p == '/' || *p == ' ') p++;
14172
14173     /* [HGM] look for Crazyhouse holdings here */
14174     while(*p==' ') p++;
14175     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14176         if(*p == '[') p++;
14177         if(*p == '-' ) *p++; /* empty holdings */ else {
14178             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14179             /* if we would allow FEN reading to set board size, we would   */
14180             /* have to add holdings and shift the board read so far here   */
14181             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14182                 *p++;
14183                 if((int) piece >= (int) BlackPawn ) {
14184                     i = (int)piece - (int)BlackPawn;
14185                     i = PieceToNumber((ChessSquare)i);
14186                     if( i >= gameInfo.holdingsSize ) return FALSE;
14187                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14188                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14189                 } else {
14190                     i = (int)piece - (int)WhitePawn;
14191                     i = PieceToNumber((ChessSquare)i);
14192                     if( i >= gameInfo.holdingsSize ) return FALSE;
14193                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14194                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14195                 }
14196             }
14197         }
14198         if(*p == ']') *p++;
14199     }
14200
14201     while(*p == ' ') p++;
14202
14203     /* Active color */
14204     switch (*p++) {
14205       case 'w':
14206         *blackPlaysFirst = FALSE;
14207         break;
14208       case 'b':
14209         *blackPlaysFirst = TRUE;
14210         break;
14211       default:
14212         return FALSE;
14213     }
14214
14215     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14216     /* return the extra info in global variiables             */
14217
14218     /* set defaults in case FEN is incomplete */
14219     board[EP_STATUS] = EP_UNKNOWN;
14220     for(i=0; i<nrCastlingRights; i++ ) {
14221         board[CASTLING][i] =
14222             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14223     }   /* assume possible unless obviously impossible */
14224     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14225     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14226     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14227                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14228     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14229     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14230     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14231                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14232     FENrulePlies = 0;
14233
14234     while(*p==' ') p++;
14235     if(nrCastlingRights) {
14236       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14237           /* castling indicator present, so default becomes no castlings */
14238           for(i=0; i<nrCastlingRights; i++ ) {
14239                  board[CASTLING][i] = NoRights;
14240           }
14241       }
14242       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14243              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14244              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14245              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14246         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14247
14248         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14249             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14250             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14251         }
14252         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14253             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14254         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14255                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14256         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14257                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14258         switch(c) {
14259           case'K':
14260               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14261               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14262               board[CASTLING][2] = whiteKingFile;
14263               break;
14264           case'Q':
14265               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14266               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14267               board[CASTLING][2] = whiteKingFile;
14268               break;
14269           case'k':
14270               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14271               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14272               board[CASTLING][5] = blackKingFile;
14273               break;
14274           case'q':
14275               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14276               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14277               board[CASTLING][5] = blackKingFile;
14278           case '-':
14279               break;
14280           default: /* FRC castlings */
14281               if(c >= 'a') { /* black rights */
14282                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14283                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14284                   if(i == BOARD_RGHT) break;
14285                   board[CASTLING][5] = i;
14286                   c -= AAA;
14287                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14288                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14289                   if(c > i)
14290                       board[CASTLING][3] = c;
14291                   else
14292                       board[CASTLING][4] = c;
14293               } else { /* white rights */
14294                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14295                     if(board[0][i] == WhiteKing) break;
14296                   if(i == BOARD_RGHT) break;
14297                   board[CASTLING][2] = i;
14298                   c -= AAA - 'a' + 'A';
14299                   if(board[0][c] >= WhiteKing) break;
14300                   if(c > i)
14301                       board[CASTLING][0] = c;
14302                   else
14303                       board[CASTLING][1] = c;
14304               }
14305         }
14306       }
14307       for(i=0; i<nrCastlingRights; i++)
14308         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14309     if (appData.debugMode) {
14310         fprintf(debugFP, "FEN castling rights:");
14311         for(i=0; i<nrCastlingRights; i++)
14312         fprintf(debugFP, " %d", board[CASTLING][i]);
14313         fprintf(debugFP, "\n");
14314     }
14315
14316       while(*p==' ') p++;
14317     }
14318
14319     /* read e.p. field in games that know e.p. capture */
14320     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14321        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14322       if(*p=='-') {
14323         p++; board[EP_STATUS] = EP_NONE;
14324       } else {
14325          char c = *p++ - AAA;
14326
14327          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14328          if(*p >= '0' && *p <='9') *p++;
14329          board[EP_STATUS] = c;
14330       }
14331     }
14332
14333
14334     if(sscanf(p, "%d", &i) == 1) {
14335         FENrulePlies = i; /* 50-move ply counter */
14336         /* (The move number is still ignored)    */
14337     }
14338
14339     return TRUE;
14340 }
14341
14342 void
14343 EditPositionPasteFEN(char *fen)
14344 {
14345   if (fen != NULL) {
14346     Board initial_position;
14347
14348     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14349       DisplayError(_("Bad FEN position in clipboard"), 0);
14350       return ;
14351     } else {
14352       int savedBlackPlaysFirst = blackPlaysFirst;
14353       EditPositionEvent();
14354       blackPlaysFirst = savedBlackPlaysFirst;
14355       CopyBoard(boards[0], initial_position);
14356       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14357       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14358       DisplayBothClocks();
14359       DrawPosition(FALSE, boards[currentMove]);
14360     }
14361   }
14362 }
14363
14364 static char cseq[12] = "\\   ";
14365
14366 Boolean set_cont_sequence(char *new_seq)
14367 {
14368     int len;
14369     Boolean ret;
14370
14371     // handle bad attempts to set the sequence
14372         if (!new_seq)
14373                 return 0; // acceptable error - no debug
14374
14375     len = strlen(new_seq);
14376     ret = (len > 0) && (len < sizeof(cseq));
14377     if (ret)
14378         strcpy(cseq, new_seq);
14379     else if (appData.debugMode)
14380         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14381     return ret;
14382 }
14383
14384 /*
14385     reformat a source message so words don't cross the width boundary.  internal
14386     newlines are not removed.  returns the wrapped size (no null character unless
14387     included in source message).  If dest is NULL, only calculate the size required
14388     for the dest buffer.  lp argument indicats line position upon entry, and it's
14389     passed back upon exit.
14390 */
14391 int wrap(char *dest, char *src, int count, int width, int *lp)
14392 {
14393     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14394
14395     cseq_len = strlen(cseq);
14396     old_line = line = *lp;
14397     ansi = len = clen = 0;
14398
14399     for (i=0; i < count; i++)
14400     {
14401         if (src[i] == '\033')
14402             ansi = 1;
14403
14404         // if we hit the width, back up
14405         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14406         {
14407             // store i & len in case the word is too long
14408             old_i = i, old_len = len;
14409
14410             // find the end of the last word
14411             while (i && src[i] != ' ' && src[i] != '\n')
14412             {
14413                 i--;
14414                 len--;
14415             }
14416
14417             // word too long?  restore i & len before splitting it
14418             if ((old_i-i+clen) >= width)
14419             {
14420                 i = old_i;
14421                 len = old_len;
14422             }
14423
14424             // extra space?
14425             if (i && src[i-1] == ' ')
14426                 len--;
14427
14428             if (src[i] != ' ' && src[i] != '\n')
14429             {
14430                 i--;
14431                 if (len)
14432                     len--;
14433             }
14434
14435             // now append the newline and continuation sequence
14436             if (dest)
14437                 dest[len] = '\n';
14438             len++;
14439             if (dest)
14440                 strncpy(dest+len, cseq, cseq_len);
14441             len += cseq_len;
14442             line = cseq_len;
14443             clen = cseq_len;
14444             continue;
14445         }
14446
14447         if (dest)
14448             dest[len] = src[i];
14449         len++;
14450         if (!ansi)
14451             line++;
14452         if (src[i] == '\n')
14453             line = 0;
14454         if (src[i] == 'm')
14455             ansi = 0;
14456     }
14457     if (dest && appData.debugMode)
14458     {
14459         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14460             count, width, line, len, *lp);
14461         show_bytes(debugFP, src, count);
14462         fprintf(debugFP, "\ndest: ");
14463         show_bytes(debugFP, dest, len);
14464         fprintf(debugFP, "\n");
14465     }
14466     *lp = dest ? line : old_line;
14467
14468     return len;
14469 }
14470
14471 // [HGM] vari: routines for shelving variations
14472
14473 void 
14474 PushTail(int firstMove, int lastMove)
14475 {
14476         int i, j, nrMoves = lastMove - firstMove;
14477
14478         if(appData.icsActive) { // only in local mode
14479                 forwardMostMove = currentMove; // mimic old ICS behavior
14480                 return;
14481         }
14482         if(storedGames >= MAX_VARIATIONS-1) return;
14483
14484         // push current tail of game on stack
14485         savedResult[storedGames] = gameInfo.result;
14486         savedDetails[storedGames] = gameInfo.resultDetails;
14487         gameInfo.resultDetails = NULL;
14488         savedFirst[storedGames] = firstMove;
14489         savedLast [storedGames] = lastMove;
14490         savedFramePtr[storedGames] = framePtr;
14491         framePtr -= nrMoves; // reserve space for the boards
14492         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14493             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14494             for(j=0; j<MOVE_LEN; j++)
14495                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14496             for(j=0; j<2*MOVE_LEN; j++)
14497                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14498             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14499             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14500             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14501             pvInfoList[firstMove+i-1].depth = 0;
14502             commentList[framePtr+i] = commentList[firstMove+i];
14503             commentList[firstMove+i] = NULL;
14504         }
14505
14506         storedGames++;
14507         forwardMostMove = currentMove; // truncte game so we can start variation
14508         if(storedGames == 1) GreyRevert(FALSE);
14509 }
14510
14511 Boolean
14512 PopTail(Boolean annotate)
14513 {
14514         int i, j, nrMoves;
14515         char buf[8000], moveBuf[20];
14516
14517         if(appData.icsActive) return FALSE; // only in local mode
14518         if(!storedGames) return FALSE; // sanity
14519
14520         storedGames--;
14521         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14522         nrMoves = savedLast[storedGames] - currentMove;
14523         if(annotate) {
14524                 int cnt = 10;
14525                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14526                 else strcpy(buf, "(");
14527                 for(i=currentMove; i<forwardMostMove; i++) {
14528                         if(WhiteOnMove(i))
14529                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14530                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14531                         strcat(buf, moveBuf);
14532                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14533                 }
14534                 strcat(buf, ")");
14535         }
14536         for(i=1; i<nrMoves; i++) { // copy last variation back
14537             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14538             for(j=0; j<MOVE_LEN; j++)
14539                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14540             for(j=0; j<2*MOVE_LEN; j++)
14541                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14542             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14543             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14544             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14545             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14546             commentList[currentMove+i] = commentList[framePtr+i];
14547             commentList[framePtr+i] = NULL;
14548         }
14549         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14550         framePtr = savedFramePtr[storedGames];
14551         gameInfo.result = savedResult[storedGames];
14552         if(gameInfo.resultDetails != NULL) {
14553             free(gameInfo.resultDetails);
14554       }
14555         gameInfo.resultDetails = savedDetails[storedGames];
14556         forwardMostMove = currentMove + nrMoves;
14557         if(storedGames == 0) GreyRevert(TRUE);
14558         return TRUE;
14559 }
14560
14561 void 
14562 CleanupTail()
14563 {       // remove all shelved variations
14564         int i;
14565         for(i=0; i<storedGames; i++) {
14566             if(savedDetails[i])
14567                 free(savedDetails[i]);
14568             savedDetails[i] = NULL;
14569         }
14570         for(i=framePtr; i<MAX_MOVES; i++) {
14571                 if(commentList[i]) free(commentList[i]);
14572                 commentList[i] = NULL;
14573         }
14574         framePtr = MAX_MOVES-1;
14575         storedGames = 0;
14576 }