Get rid of promotion popup
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 #else
135 # define _(s) (s)
136 # define N_(s) s
137 #endif
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
254 int chattingPartner;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
256
257 /* States for ics_getting_history */
258 #define H_FALSE 0
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
264
265 /* whosays values for GameEnds */
266 #define GE_ICS 0
267 #define GE_ENGINE 1
268 #define GE_PLAYER 2
269 #define GE_FILE 3
270 #define GE_XBOARD 4
271 #define GE_ENGINE1 5
272 #define GE_ENGINE2 6
273
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
276
277 /* Different types of move when calling RegisterMove */
278 #define CMAIL_MOVE   0
279 #define CMAIL_RESIGN 1
280 #define CMAIL_DRAW   2
281 #define CMAIL_ACCEPT 3
282
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
287
288 /* Telnet protocol constants */
289 #define TN_WILL 0373
290 #define TN_WONT 0374
291 #define TN_DO   0375
292 #define TN_DONT 0376
293 #define TN_IAC  0377
294 #define TN_ECHO 0001
295 #define TN_SGA  0003
296 #define TN_PORT 23
297
298 /* [AS] */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
300 {
301     assert( dst != NULL );
302     assert( src != NULL );
303     assert( count > 0 );
304
305     strncpy( dst, src, count );
306     dst[ count-1 ] = '\0';
307     return dst;
308 }
309
310 /* Some compiler can't cast u64 to double
311  * This function do the job for us:
312
313  * We use the highest bit for cast, this only
314  * works if the highest bit is not
315  * in use (This should not happen)
316  *
317  * We used this for all compiler
318  */
319 double
320 u64ToDouble(u64 value)
321 {
322   double r;
323   u64 tmp = value & u64Const(0x7fffffffffffffff);
324   r = (double)(s64)tmp;
325   if (value & u64Const(0x8000000000000000))
326        r +=  9.2233720368547758080e18; /* 2^63 */
327  return r;
328 }
329
330 /* Fake up flags for now, as we aren't keeping track of castling
331    availability yet. [HGM] Change of logic: the flag now only
332    indicates the type of castlings allowed by the rule of the game.
333    The actual rights themselves are maintained in the array
334    castlingRights, as part of the game history, and are not probed
335    by this function.
336  */
337 int
338 PosFlags(index)
339 {
340   int flags = F_ALL_CASTLE_OK;
341   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342   switch (gameInfo.variant) {
343   case VariantSuicide:
344     flags &= ~F_ALL_CASTLE_OK;
345   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346     flags |= F_IGNORE_CHECK;
347   case VariantLosers:
348     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
349     break;
350   case VariantAtomic:
351     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
352     break;
353   case VariantKriegspiel:
354     flags |= F_KRIEGSPIEL_CAPTURE;
355     break;
356   case VariantCapaRandom:
357   case VariantFischeRandom:
358     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359   case VariantNoCastle:
360   case VariantShatranj:
361   case VariantCourier:
362   case VariantMakruk:
363     flags &= ~F_ALL_CASTLE_OK;
364     break;
365   default:
366     break;
367   }
368   return flags;
369 }
370
371 FILE *gameFileFP, *debugFP;
372
373 /*
374     [AS] Note: sometimes, the sscanf() function is used to parse the input
375     into a fixed-size buffer. Because of this, we must be prepared to
376     receive strings as long as the size of the input buffer, which is currently
377     set to 4K for Windows and 8K for the rest.
378     So, we must either allocate sufficiently large buffers here, or
379     reduce the size of the input buffer in the input reading part.
380 */
381
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
385
386 ChessProgramState first, second;
387
388 /* premove variables */
389 int premoveToX = 0;
390 int premoveToY = 0;
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
394 int gotPremove = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
397
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
400
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
428 int movesPerSession;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
433 int matchGame = 0;
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
437
438 /* animateTraining preserves the state of appData.animate
439  * when Training mode is activated. This allows the
440  * response to be animated when appData.animate == TRUE and
441  * appData.animateDragging == TRUE.
442  */
443 Boolean animateTraining;
444
445 GameInfo gameInfo;
446
447 AppData appData;
448
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char  initialRights[BOARD_FILES];
453 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int   initialRulePlies, FENrulePlies;
455 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
456 int loadFlag = 0;
457 int shuffleOpenings;
458 int mute; // mute all sounds
459
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
463 int storedGames = 0;
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
469
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
473
474 ChessSquare  FIDEArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackKing, BlackKnight, BlackRook }
486 };
487
488 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
489     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491     { BlackRook, BlackMan, BlackBishop, BlackQueen,
492         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
493 };
494
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
500 };
501
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackMan, BlackFerz,
513         BlackKing, BlackMan, BlackKnight, BlackRook }
514 };
515
516
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
523 };
524
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
530 };
531
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
534         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
536         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
537 };
538
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
541         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
543         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
544 };
545
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
548         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
550         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
551 };
552
553 #ifdef GOTHIC
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
556         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
558         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !GOTHIC
561 #define GothicArray CapablancaArray
562 #endif // !GOTHIC
563
564 #ifdef FALCON
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
567         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
569         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
570 };
571 #else // !FALCON
572 #define FalconArray CapablancaArray
573 #endif // !FALCON
574
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
581
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
588 };
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
592
593
594 Board initialPosition;
595
596
597 /* Convert str to a rating. Checks for special cases of "----",
598
599    "++++", etc. Also strips ()'s */
600 int
601 string_to_rating(str)
602   char *str;
603 {
604   while(*str && !isdigit(*str)) ++str;
605   if (!*str)
606     return 0;   /* One of the special "no rating" cases */
607   else
608     return atoi(str);
609 }
610
611 void
612 ClearProgramStats()
613 {
614     /* Init programStats */
615     programStats.movelist[0] = 0;
616     programStats.depth = 0;
617     programStats.nr_moves = 0;
618     programStats.moves_left = 0;
619     programStats.nodes = 0;
620     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
621     programStats.score = 0;
622     programStats.got_only_move = 0;
623     programStats.got_fail = 0;
624     programStats.line_is_book = 0;
625 }
626
627 void
628 InitBackEnd1()
629 {
630     int matched, min, sec;
631
632     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
633
634     GetTimeMark(&programStartTime);
635     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
636
637     ClearProgramStats();
638     programStats.ok_to_send = 1;
639     programStats.seen_stat = 0;
640
641     /*
642      * Initialize game list
643      */
644     ListNew(&gameList);
645
646
647     /*
648      * Internet chess server status
649      */
650     if (appData.icsActive) {
651         appData.matchMode = FALSE;
652         appData.matchGames = 0;
653 #if ZIPPY
654         appData.noChessProgram = !appData.zippyPlay;
655 #else
656         appData.zippyPlay = FALSE;
657         appData.zippyTalk = FALSE;
658         appData.noChessProgram = TRUE;
659 #endif
660         if (*appData.icsHelper != NULLCHAR) {
661             appData.useTelnet = TRUE;
662             appData.telnetProgram = appData.icsHelper;
663         }
664     } else {
665         appData.zippyTalk = appData.zippyPlay = FALSE;
666     }
667
668     /* [AS] Initialize pv info list [HGM] and game state */
669     {
670         int i, j;
671
672         for( i=0; i<=framePtr; i++ ) {
673             pvInfoList[i].depth = -1;
674             boards[i][EP_STATUS] = EP_NONE;
675             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
676         }
677     }
678
679     /*
680      * Parse timeControl resource
681      */
682     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683                           appData.movesPerSession)) {
684         char buf[MSG_SIZ];
685         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686         DisplayFatalError(buf, 0, 2);
687     }
688
689     /*
690      * Parse searchTime resource
691      */
692     if (*appData.searchTime != NULLCHAR) {
693         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
694         if (matched == 1) {
695             searchTime = min * 60;
696         } else if (matched == 2) {
697             searchTime = min * 60 + sec;
698         } else {
699             char buf[MSG_SIZ];
700             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701             DisplayFatalError(buf, 0, 2);
702         }
703     }
704
705     /* [AS] Adjudication threshold */
706     adjudicateLossThreshold = appData.adjudicateLossThreshold;
707
708     first.which = "first";
709     second.which = "second";
710     first.maybeThinking = second.maybeThinking = FALSE;
711     first.pr = second.pr = NoProc;
712     first.isr = second.isr = NULL;
713     first.sendTime = second.sendTime = 2;
714     first.sendDrawOffers = 1;
715     if (appData.firstPlaysBlack) {
716         first.twoMachinesColor = "black\n";
717         second.twoMachinesColor = "white\n";
718     } else {
719         first.twoMachinesColor = "white\n";
720         second.twoMachinesColor = "black\n";
721     }
722     first.program = appData.firstChessProgram;
723     second.program = appData.secondChessProgram;
724     first.host = appData.firstHost;
725     second.host = appData.secondHost;
726     first.dir = appData.firstDirectory;
727     second.dir = appData.secondDirectory;
728     first.other = &second;
729     second.other = &first;
730     first.initString = appData.initString;
731     second.initString = appData.secondInitString;
732     first.computerString = appData.firstComputerString;
733     second.computerString = appData.secondComputerString;
734     first.useSigint = second.useSigint = TRUE;
735     first.useSigterm = second.useSigterm = TRUE;
736     first.reuse = appData.reuseFirst;
737     second.reuse = appData.reuseSecond;
738     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
739     second.nps = appData.secondNPS;
740     first.useSetboard = second.useSetboard = FALSE;
741     first.useSAN = second.useSAN = FALSE;
742     first.usePing = second.usePing = FALSE;
743     first.lastPing = second.lastPing = 0;
744     first.lastPong = second.lastPong = 0;
745     first.usePlayother = second.usePlayother = FALSE;
746     first.useColors = second.useColors = TRUE;
747     first.useUsermove = second.useUsermove = FALSE;
748     first.sendICS = second.sendICS = FALSE;
749     first.sendName = second.sendName = appData.icsActive;
750     first.sdKludge = second.sdKludge = FALSE;
751     first.stKludge = second.stKludge = FALSE;
752     TidyProgramName(first.program, first.host, first.tidy);
753     TidyProgramName(second.program, second.host, second.tidy);
754     first.matchWins = second.matchWins = 0;
755     strcpy(first.variants, appData.variant);
756     strcpy(second.variants, appData.variant);
757     first.analysisSupport = second.analysisSupport = 2; /* detect */
758     first.analyzing = second.analyzing = FALSE;
759     first.initDone = second.initDone = FALSE;
760
761     /* New features added by Tord: */
762     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764     /* End of new features added by Tord. */
765     first.fenOverride  = appData.fenOverride1;
766     second.fenOverride = appData.fenOverride2;
767
768     /* [HGM] time odds: set factor for each machine */
769     first.timeOdds  = appData.firstTimeOdds;
770     second.timeOdds = appData.secondTimeOdds;
771     { float norm = 1;
772         if(appData.timeOddsMode) {
773             norm = first.timeOdds;
774             if(norm > second.timeOdds) norm = second.timeOdds;
775         }
776         first.timeOdds /= norm;
777         second.timeOdds /= norm;
778     }
779
780     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781     first.accumulateTC = appData.firstAccumulateTC;
782     second.accumulateTC = appData.secondAccumulateTC;
783     first.maxNrOfSessions = second.maxNrOfSessions = 1;
784
785     /* [HGM] debug */
786     first.debug = second.debug = FALSE;
787     first.supportsNPS = second.supportsNPS = UNKNOWN;
788
789     /* [HGM] options */
790     first.optionSettings  = appData.firstOptions;
791     second.optionSettings = appData.secondOptions;
792
793     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795     first.isUCI = appData.firstIsUCI; /* [AS] */
796     second.isUCI = appData.secondIsUCI; /* [AS] */
797     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
799
800     if (appData.firstProtocolVersion > PROTOVER ||
801         appData.firstProtocolVersion < 1) {
802       char buf[MSG_SIZ];
803       sprintf(buf, _("protocol version %d not supported"),
804               appData.firstProtocolVersion);
805       DisplayFatalError(buf, 0, 2);
806     } else {
807       first.protocolVersion = appData.firstProtocolVersion;
808     }
809
810     if (appData.secondProtocolVersion > PROTOVER ||
811         appData.secondProtocolVersion < 1) {
812       char buf[MSG_SIZ];
813       sprintf(buf, _("protocol version %d not supported"),
814               appData.secondProtocolVersion);
815       DisplayFatalError(buf, 0, 2);
816     } else {
817       second.protocolVersion = appData.secondProtocolVersion;
818     }
819
820     if (appData.icsActive) {
821         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
822 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824         appData.clockMode = FALSE;
825         first.sendTime = second.sendTime = 0;
826     }
827
828 #if ZIPPY
829     /* Override some settings from environment variables, for backward
830        compatibility.  Unfortunately it's not feasible to have the env
831        vars just set defaults, at least in xboard.  Ugh.
832     */
833     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
834       ZippyInit();
835     }
836 #endif
837
838     if (appData.noChessProgram) {
839         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840         sprintf(programVersion, "%s", PACKAGE_STRING);
841     } else {
842       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
845     }
846
847     if (!appData.icsActive) {
848       char buf[MSG_SIZ];
849       /* Check for variants that are supported only in ICS mode,
850          or not at all.  Some that are accepted here nevertheless
851          have bugs; see comments below.
852       */
853       VariantClass variant = StringToVariant(appData.variant);
854       switch (variant) {
855       case VariantBughouse:     /* need four players and two boards */
856       case VariantKriegspiel:   /* need to hide pieces and move details */
857       /* case VariantFischeRandom: (Fabien: moved below) */
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859         DisplayFatalError(buf, 0, 2);
860         return;
861
862       case VariantUnknown:
863       case VariantLoadable:
864       case Variant29:
865       case Variant30:
866       case Variant31:
867       case Variant32:
868       case Variant33:
869       case Variant34:
870       case Variant35:
871       case Variant36:
872       default:
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);
874         DisplayFatalError(buf, 0, 2);
875         return;
876
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */
879       case VariantGothic:     /* [HGM] should work */
880       case VariantCapablanca: /* [HGM] should work */
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */
882       case VariantShogi:      /* [HGM] drops not tested for legality */
883       case VariantKnightmate: /* [HGM] should work */
884       case VariantCylinder:   /* [HGM] untested */
885       case VariantFalcon:     /* [HGM] untested */
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887                                  offboard interposition not understood */
888       case VariantNormal:     /* definitely works! */
889       case VariantWildCastle: /* pieces not automatically shuffled */
890       case VariantNoCastle:   /* pieces not automatically shuffled */
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892       case VariantLosers:     /* should work except for win condition,
893                                  and doesn't know captures are mandatory */
894       case VariantSuicide:    /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantGiveaway:   /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantTwoKings:   /* should work */
899       case VariantAtomic:     /* should work except for win condition */
900       case Variant3Check:     /* should work except for win condition */
901       case VariantShatranj:   /* should work except for all win conditions */
902       case VariantMakruk:     /* should work except for daw countdown */
903       case VariantBerolina:   /* might work if TestLegality is off */
904       case VariantCapaRandom: /* should work */
905       case VariantJanus:      /* should work */
906       case VariantSuper:      /* experimental */
907       case VariantGreat:      /* experimental, requires legality testing to be off */
908         break;
909       }
910     }
911
912     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
913     InitEngineUCI( installDir, &second );
914 }
915
916 int NextIntegerFromString( char ** str, long * value )
917 {
918     int result = -1;
919     char * s = *str;
920
921     while( *s == ' ' || *s == '\t' ) {
922         s++;
923     }
924
925     *value = 0;
926
927     if( *s >= '0' && *s <= '9' ) {
928         while( *s >= '0' && *s <= '9' ) {
929             *value = *value * 10 + (*s - '0');
930             s++;
931         }
932
933         result = 0;
934     }
935
936     *str = s;
937
938     return result;
939 }
940
941 int NextTimeControlFromString( char ** str, long * value )
942 {
943     long temp;
944     int result = NextIntegerFromString( str, &temp );
945
946     if( result == 0 ) {
947         *value = temp * 60; /* Minutes */
948         if( **str == ':' ) {
949             (*str)++;
950             result = NextIntegerFromString( str, &temp );
951             *value += temp; /* Seconds */
952         }
953     }
954
955     return result;
956 }
957
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
960     int result = -1; long temp, temp2;
961
962     if(**str != '+') return -1; // old params remain in force!
963     (*str)++;
964     if( NextTimeControlFromString( str, &temp ) ) return -1;
965
966     if(**str != '/') {
967         /* time only: incremental or sudden-death time control */
968         if(**str == '+') { /* increment follows; read it */
969             (*str)++;
970             if(result = NextIntegerFromString( str, &temp2)) return -1;
971             *inc = temp2 * 1000;
972         } else *inc = 0;
973         *moves = 0; *tc = temp * 1000;
974         return 0;
975     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
976
977     (*str)++; /* classical time control */
978     result = NextTimeControlFromString( str, &temp2);
979     if(result == 0) {
980         *moves = temp/60;
981         *tc    = temp2 * 1000;
982         *inc   = 0;
983     }
984     return result;
985 }
986
987 int GetTimeQuota(int movenr)
988 {   /* [HGM] get time to add from the multi-session time-control string */
989     int moves=1; /* kludge to force reading of first session */
990     long time, increment;
991     char *s = fullTimeControlString;
992
993     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994     do {
995         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997         if(movenr == -1) return time;    /* last move before new session     */
998         if(!moves) return increment;     /* current session is incremental   */
999         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000     } while(movenr >= -1);               /* try again for next session       */
1001
1002     return 0; // no new time quota on this move
1003 }
1004
1005 int
1006 ParseTimeControl(tc, ti, mps)
1007      char *tc;
1008      int ti;
1009      int mps;
1010 {
1011   long tc1;
1012   long tc2;
1013   char buf[MSG_SIZ];
1014   
1015   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1016   if(ti > 0) {
1017     if(mps)
1018       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019     else sprintf(buf, "+%s+%d", tc, ti);
1020   } else {
1021     if(mps)
1022              sprintf(buf, "+%d/%s", mps, tc);
1023     else sprintf(buf, "+%s", tc);
1024   }
1025   fullTimeControlString = StrSave(buf);
1026   
1027   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1028     return FALSE;
1029   }
1030   
1031   if( *tc == '/' ) {
1032     /* Parse second time control */
1033     tc++;
1034     
1035     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1036       return FALSE;
1037     }
1038     
1039     if( tc2 == 0 ) {
1040       return FALSE;
1041     }
1042     
1043     timeControl_2 = tc2 * 1000;
1044   }
1045   else {
1046     timeControl_2 = 0;
1047   }
1048   
1049   if( tc1 == 0 ) {
1050     return FALSE;
1051   }
1052   
1053   timeControl = tc1 * 1000;
1054   
1055   if (ti >= 0) {
1056     timeIncrement = ti * 1000;  /* convert to ms */
1057     movesPerSession = 0;
1058   } else {
1059     timeIncrement = 0;
1060     movesPerSession = mps;
1061   }
1062   return TRUE;
1063 }
1064
1065 void
1066 InitBackEnd2()
1067 {
1068   if (appData.debugMode) {
1069     fprintf(debugFP, "%s\n", programVersion);
1070   }
1071
1072   set_cont_sequence(appData.wrapContSeq);
1073   if (appData.matchGames > 0) {
1074     appData.matchMode = TRUE;
1075   } else if (appData.matchMode) {
1076     appData.matchGames = 1;
1077   }
1078   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079     appData.matchGames = appData.sameColorGames;
1080   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1083   }
1084   Reset(TRUE, FALSE);
1085   if (appData.noChessProgram || first.protocolVersion == 1) {
1086     InitBackEnd3();
1087     } else {
1088     /* kludge: allow timeout for initial "feature" commands */
1089     FreezeUI();
1090     DisplayMessage("", _("Starting chess program"));
1091     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1092   }
1093 }
1094
1095 void
1096 InitBackEnd3 P((void))
1097 {
1098   GameMode initialMode;
1099   char buf[MSG_SIZ];
1100   int err;
1101   
1102   InitChessProgram(&first, startedFromSetupPosition);
1103   
1104   
1105   if (appData.icsActive) 
1106     {
1107       err = establish();
1108       if (err != 0) 
1109         {
1110           if (*appData.icsCommPort != NULLCHAR) 
1111             {
1112               sprintf(buf, _("Could not open comm port %s"),
1113                       appData.icsCommPort);
1114             }
1115           else 
1116             {
1117               snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1118                        appData.icsHost, appData.icsPort);
1119             }
1120           DisplayFatalError(buf, err, 1);
1121           return;
1122         }
1123
1124         SetICSMode();
1125         telnetISR =
1126           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1127         fromUserISR =
1128           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1131     }
1132   else if (appData.noChessProgram) 
1133     {
1134       SetNCPMode();
1135     } 
1136   else 
1137     {
1138       SetGNUMode();
1139     }
1140   
1141   if (*appData.cmailGameName != NULLCHAR) 
1142     {
1143       SetCmailMode();
1144       OpenLoopback(&cmailPR);
1145       cmailISR =
1146         AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1147     }
1148   
1149   ThawUI();
1150   DisplayMessage("", "");
1151   if (StrCaseCmp(appData.initialMode, "") == 0) 
1152     {
1153       initialMode = BeginningOfGame;
1154     } 
1155   else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) 
1156     {
1157       initialMode = TwoMachinesPlay;
1158     } 
1159   else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) 
1160     {
1161       initialMode = AnalyzeFile;
1162     } 
1163   else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) 
1164     {
1165       initialMode = AnalyzeMode;
1166     } 
1167   else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) 
1168     {
1169       initialMode = MachinePlaysWhite;
1170     } 
1171   else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) 
1172     {
1173       initialMode = MachinePlaysBlack;
1174     } 
1175   else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) 
1176     {
1177       initialMode = EditGame;
1178     } 
1179   else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) 
1180     {
1181       initialMode = EditPosition;
1182     } 
1183   else if (StrCaseCmp(appData.initialMode, "Training") == 0) 
1184     {
1185       initialMode = Training;
1186     } 
1187   else 
1188     {
1189       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1190       DisplayFatalError(buf, 0, 2);
1191       return;
1192     }
1193   
1194   if (appData.matchMode) 
1195     {
1196       /* Set up machine vs. machine match */
1197       if (appData.noChessProgram) 
1198         {
1199           DisplayFatalError(_("Can't have a match with no chess programs"),
1200                             0, 2);
1201           return;
1202         }
1203       matchMode = TRUE;
1204       matchGame = 1;
1205       if (*appData.loadGameFile != NULLCHAR) 
1206         {
1207           int index = appData.loadGameIndex; // [HGM] autoinc
1208           if(index<0) lastIndex = index = 1;
1209           if (!LoadGameFromFile(appData.loadGameFile,
1210                                 index,
1211                                 appData.loadGameFile, FALSE)) 
1212             {
1213               DisplayFatalError(_("Bad game file"), 0, 1);
1214               return;
1215             }
1216         } 
1217       else if (*appData.loadPositionFile != NULLCHAR) 
1218         {
1219           int index = appData.loadPositionIndex; // [HGM] autoinc
1220           if(index<0) lastIndex = index = 1;
1221           if (!LoadPositionFromFile(appData.loadPositionFile,
1222                                     index,
1223                                     appData.loadPositionFile)) 
1224             {
1225               DisplayFatalError(_("Bad position file"), 0, 1);
1226               return;
1227             }
1228         }
1229       TwoMachinesEvent();
1230     } 
1231   else if (*appData.cmailGameName != NULLCHAR) 
1232     {
1233       /* Set up cmail mode */
1234       ReloadCmailMsgEvent(TRUE);
1235     } 
1236   else 
1237     {
1238       /* Set up other modes */
1239       if (initialMode == AnalyzeFile) 
1240         {
1241           if (*appData.loadGameFile == NULLCHAR) 
1242             {
1243               DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1244               return;
1245             }
1246         }
1247       if (*appData.loadGameFile != NULLCHAR) 
1248         {
1249           (void) LoadGameFromFile(appData.loadGameFile,
1250                                   appData.loadGameIndex,
1251                                   appData.loadGameFile, TRUE);
1252         } 
1253       else if (*appData.loadPositionFile != NULLCHAR) 
1254         {
1255           (void) LoadPositionFromFile(appData.loadPositionFile,
1256                                       appData.loadPositionIndex,
1257                                       appData.loadPositionFile);
1258           /* [HGM] try to make self-starting even after FEN load */
1259           /* to allow automatic setup of fairy variants with wtm */
1260           if(initialMode == BeginningOfGame && !blackPlaysFirst) 
1261             {
1262               gameMode = BeginningOfGame;
1263               setboardSpoiledMachineBlack = 1;
1264             }
1265           /* [HGM] loadPos: make that every new game uses the setup */
1266           /* from file as long as we do not switch variant          */
1267           if(!blackPlaysFirst) 
1268             {
1269               startedFromPositionFile = TRUE;
1270               CopyBoard(filePosition, boards[0]);
1271             }
1272         }
1273       if (initialMode == AnalyzeMode) 
1274         {
1275           if (appData.noChessProgram) 
1276             {
1277               DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1278               return;
1279             }
1280           if (appData.icsActive) 
1281             {
1282               DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1283               return;
1284             }
1285           AnalyzeModeEvent();
1286         } 
1287       else if (initialMode == AnalyzeFile) 
1288         {
1289           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1290           ShowThinkingEvent();
1291           AnalyzeFileEvent();
1292           AnalysisPeriodicEvent(1);
1293         } 
1294       else if (initialMode == MachinePlaysWhite) 
1295         {
1296           if (appData.noChessProgram) 
1297             {
1298               DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1299                                 0, 2);
1300               return;
1301             }
1302           if (appData.icsActive) 
1303             {
1304               DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1305                                 0, 2);
1306               return;
1307             }
1308           MachineWhiteEvent();
1309         } 
1310       else if (initialMode == MachinePlaysBlack) 
1311         {
1312           if (appData.noChessProgram) 
1313             {
1314               DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1315                                 0, 2);
1316               return;
1317             }
1318           if (appData.icsActive) 
1319             {
1320               DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1321                                 0, 2);
1322               return;
1323             }
1324           MachineBlackEvent();
1325         } 
1326       else if (initialMode == TwoMachinesPlay) 
1327         {
1328           if (appData.noChessProgram) 
1329             {
1330               DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1331                                 0, 2);
1332               return;
1333             }
1334           if (appData.icsActive) 
1335             {
1336               DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1337                                 0, 2);
1338               return;
1339             }
1340           TwoMachinesEvent();
1341         } 
1342       else if (initialMode == EditGame) 
1343         {
1344           EditGameEvent();
1345         } 
1346       else if (initialMode == EditPosition) 
1347         {
1348           EditPositionEvent();
1349         } 
1350       else if (initialMode == Training) 
1351         {
1352           if (*appData.loadGameFile == NULLCHAR) 
1353             {
1354               DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1355               return;
1356             }
1357           TrainingEvent();
1358         }
1359     }
1360   
1361   return;
1362 }
1363
1364 /*
1365  * Establish will establish a contact to a remote host.port.
1366  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1367  *  used to talk to the host.
1368  * Returns 0 if okay, error code if not.
1369  */
1370 int
1371 establish()
1372 {
1373     char buf[MSG_SIZ];
1374
1375     if (*appData.icsCommPort != NULLCHAR) {
1376         /* Talk to the host through a serial comm port */
1377         return OpenCommPort(appData.icsCommPort, &icsPR);
1378
1379     } else if (*appData.gateway != NULLCHAR) {
1380         if (*appData.remoteShell == NULLCHAR) {
1381             /* Use the rcmd protocol to run telnet program on a gateway host */
1382             snprintf(buf, sizeof(buf), "%s %s %s",
1383                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1384             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1385
1386         } else {
1387             /* Use the rsh program to run telnet program on a gateway host */
1388             if (*appData.remoteUser == NULLCHAR) {
1389                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1390                         appData.gateway, appData.telnetProgram,
1391                         appData.icsHost, appData.icsPort);
1392             } else {
1393                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1394                         appData.remoteShell, appData.gateway,
1395                         appData.remoteUser, appData.telnetProgram,
1396                         appData.icsHost, appData.icsPort);
1397             }
1398             return StartChildProcess(buf, "", &icsPR);
1399
1400         }
1401     } else if (appData.useTelnet) {
1402         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1403
1404     } else {
1405         /* TCP socket interface differs somewhat between
1406            Unix and NT; handle details in the front end.
1407            */
1408         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1409     }
1410 }
1411
1412 void
1413 show_bytes(fp, buf, count)
1414      FILE *fp;
1415      char *buf;
1416      int count;
1417 {
1418     while (count--) {
1419         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1420             fprintf(fp, "\\%03o", *buf & 0xff);
1421         } else {
1422             putc(*buf, fp);
1423         }
1424         buf++;
1425     }
1426     fflush(fp);
1427 }
1428
1429 /* Returns an errno value */
1430 int
1431 OutputMaybeTelnet(pr, message, count, outError)
1432      ProcRef pr;
1433      char *message;
1434      int count;
1435      int *outError;
1436 {
1437     char buf[8192], *p, *q, *buflim;
1438     int left, newcount, outcount;
1439
1440     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1441         *appData.gateway != NULLCHAR) {
1442         if (appData.debugMode) {
1443             fprintf(debugFP, ">ICS: ");
1444             show_bytes(debugFP, message, count);
1445             fprintf(debugFP, "\n");
1446         }
1447         return OutputToProcess(pr, message, count, outError);
1448     }
1449
1450     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1451     p = message;
1452     q = buf;
1453     left = count;
1454     newcount = 0;
1455     while (left) {
1456         if (q >= buflim) {
1457             if (appData.debugMode) {
1458                 fprintf(debugFP, ">ICS: ");
1459                 show_bytes(debugFP, buf, newcount);
1460                 fprintf(debugFP, "\n");
1461             }
1462             outcount = OutputToProcess(pr, buf, newcount, outError);
1463             if (outcount < newcount) return -1; /* to be sure */
1464             q = buf;
1465             newcount = 0;
1466         }
1467         if (*p == '\n') {
1468             *q++ = '\r';
1469             newcount++;
1470         } else if (((unsigned char) *p) == TN_IAC) {
1471             *q++ = (char) TN_IAC;
1472             newcount ++;
1473         }
1474         *q++ = *p++;
1475         newcount++;
1476         left--;
1477     }
1478     if (appData.debugMode) {
1479         fprintf(debugFP, ">ICS: ");
1480         show_bytes(debugFP, buf, newcount);
1481         fprintf(debugFP, "\n");
1482     }
1483     outcount = OutputToProcess(pr, buf, newcount, outError);
1484     if (outcount < newcount) return -1; /* to be sure */
1485     return count;
1486 }
1487
1488 void
1489 read_from_player(isr, closure, message, count, error)
1490      InputSourceRef isr;
1491      VOIDSTAR closure;
1492      char *message;
1493      int count;
1494      int error;
1495 {
1496     int outError, outCount;
1497     static int gotEof = 0;
1498
1499     /* Pass data read from player on to ICS */
1500     if (count > 0) {
1501         gotEof = 0;
1502         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1503         if (outCount < count) {
1504             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1505         }
1506     } else if (count < 0) {
1507         RemoveInputSource(isr);
1508         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1509     } else if (gotEof++ > 0) {
1510         RemoveInputSource(isr);
1511         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1512     }
1513 }
1514
1515 void
1516 KeepAlive()
1517 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1518     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1519     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1520     SendToICS("date\n");
1521     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1522 }
1523
1524 /* added routine for printf style output to ics */
1525 void ics_printf(char *format, ...)
1526 {
1527     char buffer[MSG_SIZ];
1528     va_list args;
1529
1530     va_start(args, format);
1531     vsnprintf(buffer, sizeof(buffer), format, args);
1532     buffer[sizeof(buffer)-1] = '\0';
1533     SendToICS(buffer);
1534     va_end(args);
1535 }
1536
1537 void
1538 SendToICS(s)
1539      char *s;
1540 {
1541     int count, outCount, outError;
1542
1543     if (icsPR == NULL) return;
1544
1545     count = strlen(s);
1546     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1547     if (outCount < count) {
1548         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1549     }
1550 }
1551
1552 /* This is used for sending logon scripts to the ICS. Sending
1553    without a delay causes problems when using timestamp on ICC
1554    (at least on my machine). */
1555 void
1556 SendToICSDelayed(s,msdelay)
1557      char *s;
1558      long msdelay;
1559 {
1560     int count, outCount, outError;
1561
1562     if (icsPR == NULL) return;
1563
1564     count = strlen(s);
1565     if (appData.debugMode) {
1566         fprintf(debugFP, ">ICS: ");
1567         show_bytes(debugFP, s, count);
1568         fprintf(debugFP, "\n");
1569     }
1570     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1571                                       msdelay);
1572     if (outCount < count) {
1573         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1574     }
1575 }
1576
1577
1578 /* Remove all highlighting escape sequences in s
1579    Also deletes any suffix starting with '('
1580    */
1581 char *
1582 StripHighlightAndTitle(s)
1583      char *s;
1584 {
1585     static char retbuf[MSG_SIZ];
1586     char *p = retbuf;
1587
1588     while (*s != NULLCHAR) {
1589         while (*s == '\033') {
1590             while (*s != NULLCHAR && !isalpha(*s)) s++;
1591             if (*s != NULLCHAR) s++;
1592         }
1593         while (*s != NULLCHAR && *s != '\033') {
1594             if (*s == '(' || *s == '[') {
1595                 *p = NULLCHAR;
1596                 return retbuf;
1597             }
1598             *p++ = *s++;
1599         }
1600     }
1601     *p = NULLCHAR;
1602     return retbuf;
1603 }
1604
1605 /* Remove all highlighting escape sequences in s */
1606 char *
1607 StripHighlight(s)
1608      char *s;
1609 {
1610     static char retbuf[MSG_SIZ];
1611     char *p = retbuf;
1612
1613     while (*s != NULLCHAR) {
1614         while (*s == '\033') {
1615             while (*s != NULLCHAR && !isalpha(*s)) s++;
1616             if (*s != NULLCHAR) s++;
1617         }
1618         while (*s != NULLCHAR && *s != '\033') {
1619             *p++ = *s++;
1620         }
1621     }
1622     *p = NULLCHAR;
1623     return retbuf;
1624 }
1625
1626 char *variantNames[] = VARIANT_NAMES;
1627 char *
1628 VariantName(v)
1629      VariantClass v;
1630 {
1631     return variantNames[v];
1632 }
1633
1634
1635 /* Identify a variant from the strings the chess servers use or the
1636    PGN Variant tag names we use. */
1637 VariantClass
1638 StringToVariant(e)
1639      char *e;
1640 {
1641     char *p;
1642     int wnum = -1;
1643     VariantClass v = VariantNormal;
1644     int i, found = FALSE;
1645     char buf[MSG_SIZ];
1646
1647     if (!e) return v;
1648
1649     /* [HGM] skip over optional board-size prefixes */
1650     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1651         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1652         while( *e++ != '_');
1653     }
1654
1655     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1656         v = VariantNormal;
1657         found = TRUE;
1658     } else
1659     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1660       if (StrCaseStr(e, variantNames[i])) {
1661         v = (VariantClass) i;
1662         found = TRUE;
1663         break;
1664       }
1665     }
1666
1667     if (!found) {
1668       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1669           || StrCaseStr(e, "wild/fr")
1670           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1671         v = VariantFischeRandom;
1672       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1673                  (i = 1, p = StrCaseStr(e, "w"))) {
1674         p += i;
1675         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1676         if (isdigit(*p)) {
1677           wnum = atoi(p);
1678         } else {
1679           wnum = -1;
1680         }
1681         switch (wnum) {
1682         case 0: /* FICS only, actually */
1683         case 1:
1684           /* Castling legal even if K starts on d-file */
1685           v = VariantWildCastle;
1686           break;
1687         case 2:
1688         case 3:
1689         case 4:
1690           /* Castling illegal even if K & R happen to start in
1691              normal positions. */
1692           v = VariantNoCastle;
1693           break;
1694         case 5:
1695         case 7:
1696         case 8:
1697         case 10:
1698         case 11:
1699         case 12:
1700         case 13:
1701         case 14:
1702         case 15:
1703         case 18:
1704         case 19:
1705           /* Castling legal iff K & R start in normal positions */
1706           v = VariantNormal;
1707           break;
1708         case 6:
1709         case 20:
1710         case 21:
1711           /* Special wilds for position setup; unclear what to do here */
1712           v = VariantLoadable;
1713           break;
1714         case 9:
1715           /* Bizarre ICC game */
1716           v = VariantTwoKings;
1717           break;
1718         case 16:
1719           v = VariantKriegspiel;
1720           break;
1721         case 17:
1722           v = VariantLosers;
1723           break;
1724         case 22:
1725           v = VariantFischeRandom;
1726           break;
1727         case 23:
1728           v = VariantCrazyhouse;
1729           break;
1730         case 24:
1731           v = VariantBughouse;
1732           break;
1733         case 25:
1734           v = Variant3Check;
1735           break;
1736         case 26:
1737           /* Not quite the same as FICS suicide! */
1738           v = VariantGiveaway;
1739           break;
1740         case 27:
1741           v = VariantAtomic;
1742           break;
1743         case 28:
1744           v = VariantShatranj;
1745           break;
1746
1747         /* Temporary names for future ICC types.  The name *will* change in
1748            the next xboard/WinBoard release after ICC defines it. */
1749         case 29:
1750           v = Variant29;
1751           break;
1752         case 30:
1753           v = Variant30;
1754           break;
1755         case 31:
1756           v = Variant31;
1757           break;
1758         case 32:
1759           v = Variant32;
1760           break;
1761         case 33:
1762           v = Variant33;
1763           break;
1764         case 34:
1765           v = Variant34;
1766           break;
1767         case 35:
1768           v = Variant35;
1769           break;
1770         case 36:
1771           v = Variant36;
1772           break;
1773         case 37:
1774           v = VariantShogi;
1775           break;
1776         case 38:
1777           v = VariantXiangqi;
1778           break;
1779         case 39:
1780           v = VariantCourier;
1781           break;
1782         case 40:
1783           v = VariantGothic;
1784           break;
1785         case 41:
1786           v = VariantCapablanca;
1787           break;
1788         case 42:
1789           v = VariantKnightmate;
1790           break;
1791         case 43:
1792           v = VariantFairy;
1793           break;
1794         case 44:
1795           v = VariantCylinder;
1796           break;
1797         case 45:
1798           v = VariantFalcon;
1799           break;
1800         case 46:
1801           v = VariantCapaRandom;
1802           break;
1803         case 47:
1804           v = VariantBerolina;
1805           break;
1806         case 48:
1807           v = VariantJanus;
1808           break;
1809         case 49:
1810           v = VariantSuper;
1811           break;
1812         case 50:
1813           v = VariantGreat;
1814           break;
1815         case -1:
1816           /* Found "wild" or "w" in the string but no number;
1817              must assume it's normal chess. */
1818           v = VariantNormal;
1819           break;
1820         default:
1821           sprintf(buf, _("Unknown wild type %d"), wnum);
1822           DisplayError(buf, 0);
1823           v = VariantUnknown;
1824           break;
1825         }
1826       }
1827     }
1828     if (appData.debugMode) {
1829       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1830               e, wnum, VariantName(v));
1831     }
1832     return v;
1833 }
1834
1835 static int leftover_start = 0, leftover_len = 0;
1836 char star_match[STAR_MATCH_N][MSG_SIZ];
1837
1838 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1839    advance *index beyond it, and set leftover_start to the new value of
1840    *index; else return FALSE.  If pattern contains the character '*', it
1841    matches any sequence of characters not containing '\r', '\n', or the
1842    character following the '*' (if any), and the matched sequence(s) are
1843    copied into star_match.
1844    */
1845 int
1846 looking_at(buf, index, pattern)
1847      char *buf;
1848      int *index;
1849      char *pattern;
1850 {
1851     char *bufp = &buf[*index], *patternp = pattern;
1852     int star_count = 0;
1853     char *matchp = star_match[0];
1854
1855     for (;;) {
1856         if (*patternp == NULLCHAR) {
1857             *index = leftover_start = bufp - buf;
1858             *matchp = NULLCHAR;
1859             return TRUE;
1860         }
1861         if (*bufp == NULLCHAR) return FALSE;
1862         if (*patternp == '*') {
1863             if (*bufp == *(patternp + 1)) {
1864                 *matchp = NULLCHAR;
1865                 matchp = star_match[++star_count];
1866                 patternp += 2;
1867                 bufp++;
1868                 continue;
1869             } else if (*bufp == '\n' || *bufp == '\r') {
1870                 patternp++;
1871                 if (*patternp == NULLCHAR)
1872                   continue;
1873                 else
1874                   return FALSE;
1875             } else {
1876                 *matchp++ = *bufp++;
1877                 continue;
1878             }
1879         }
1880         if (*patternp != *bufp) return FALSE;
1881         patternp++;
1882         bufp++;
1883     }
1884 }
1885
1886 void
1887 SendToPlayer(data, length)
1888      char *data;
1889      int length;
1890 {
1891     int error, outCount;
1892     outCount = OutputToProcess(NoProc, data, length, &error);
1893     if (outCount < length) {
1894         DisplayFatalError(_("Error writing to display"), error, 1);
1895     }
1896 }
1897
1898 void
1899 PackHolding(packed, holding)
1900      char packed[];
1901      char *holding;
1902 {
1903     char *p = holding;
1904     char *q = packed;
1905     int runlength = 0;
1906     int curr = 9999;
1907     do {
1908         if (*p == curr) {
1909             runlength++;
1910         } else {
1911             switch (runlength) {
1912               case 0:
1913                 break;
1914               case 1:
1915                 *q++ = curr;
1916                 break;
1917               case 2:
1918                 *q++ = curr;
1919                 *q++ = curr;
1920                 break;
1921               default:
1922                 sprintf(q, "%d", runlength);
1923                 while (*q) q++;
1924                 *q++ = curr;
1925                 break;
1926             }
1927             runlength = 1;
1928             curr = *p;
1929         }
1930     } while (*p++);
1931     *q = NULLCHAR;
1932 }
1933
1934 /* Telnet protocol requests from the front end */
1935 void
1936 TelnetRequest(ddww, option)
1937      unsigned char ddww, option;
1938 {
1939     unsigned char msg[3];
1940     int outCount, outError;
1941
1942     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1943
1944     if (appData.debugMode) {
1945         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1946         switch (ddww) {
1947           case TN_DO:
1948             ddwwStr = "DO";
1949             break;
1950           case TN_DONT:
1951             ddwwStr = "DONT";
1952             break;
1953           case TN_WILL:
1954             ddwwStr = "WILL";
1955             break;
1956           case TN_WONT:
1957             ddwwStr = "WONT";
1958             break;
1959           default:
1960             ddwwStr = buf1;
1961             sprintf(buf1, "%d", ddww);
1962             break;
1963         }
1964         switch (option) {
1965           case TN_ECHO:
1966             optionStr = "ECHO";
1967             break;
1968           default:
1969             optionStr = buf2;
1970             sprintf(buf2, "%d", option);
1971             break;
1972         }
1973         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1974     }
1975     msg[0] = TN_IAC;
1976     msg[1] = ddww;
1977     msg[2] = option;
1978     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1979     if (outCount < 3) {
1980         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1981     }
1982 }
1983
1984 void
1985 DoEcho()
1986 {
1987     if (!appData.icsActive) return;
1988     TelnetRequest(TN_DO, TN_ECHO);
1989 }
1990
1991 void
1992 DontEcho()
1993 {
1994     if (!appData.icsActive) return;
1995     TelnetRequest(TN_DONT, TN_ECHO);
1996 }
1997
1998 void
1999 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2000 {
2001     /* put the holdings sent to us by the server on the board holdings area */
2002     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2003     char p;
2004     ChessSquare piece;
2005
2006     if(gameInfo.holdingsWidth < 2)  return;
2007     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2008         return; // prevent overwriting by pre-board holdings
2009
2010     if( (int)lowestPiece >= BlackPawn ) {
2011         holdingsColumn = 0;
2012         countsColumn = 1;
2013         holdingsStartRow = BOARD_HEIGHT-1;
2014         direction = -1;
2015     } else {
2016         holdingsColumn = BOARD_WIDTH-1;
2017         countsColumn = BOARD_WIDTH-2;
2018         holdingsStartRow = 0;
2019         direction = 1;
2020     }
2021
2022     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2023         board[i][holdingsColumn] = EmptySquare;
2024         board[i][countsColumn]   = (ChessSquare) 0;
2025     }
2026     while( (p=*holdings++) != NULLCHAR ) {
2027         piece = CharToPiece( ToUpper(p) );
2028         if(piece == EmptySquare) continue;
2029         /*j = (int) piece - (int) WhitePawn;*/
2030         j = PieceToNumber(piece);
2031         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2032         if(j < 0) continue;               /* should not happen */
2033         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2034         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2035         board[holdingsStartRow+j*direction][countsColumn]++;
2036     }
2037 }
2038
2039
2040 void
2041 VariantSwitch(Board board, VariantClass newVariant)
2042 {
2043    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2044    Board oldBoard;
2045
2046    startedFromPositionFile = FALSE;
2047    if(gameInfo.variant == newVariant) return;
2048
2049    /* [HGM] This routine is called each time an assignment is made to
2050     * gameInfo.variant during a game, to make sure the board sizes
2051     * are set to match the new variant. If that means adding or deleting
2052     * holdings, we shift the playing board accordingly
2053     * This kludge is needed because in ICS observe mode, we get boards
2054     * of an ongoing game without knowing the variant, and learn about the
2055     * latter only later. This can be because of the move list we requested,
2056     * in which case the game history is refilled from the beginning anyway,
2057     * but also when receiving holdings of a crazyhouse game. In the latter
2058     * case we want to add those holdings to the already received position.
2059     */
2060    
2061    if (appData.debugMode) {
2062      fprintf(debugFP, "Switch board from %s to %s\n",
2063              VariantName(gameInfo.variant), VariantName(newVariant));
2064      setbuf(debugFP, NULL);
2065    }
2066    shuffleOpenings = 0;       /* [HGM] shuffle */
2067    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2068    switch(newVariant) 
2069      {
2070      case VariantShogi:
2071        newWidth = 9;  newHeight = 9;
2072        gameInfo.holdingsSize = 7;
2073      case VariantBughouse:
2074      case VariantCrazyhouse:
2075        newHoldingsWidth = 2; break;
2076      case VariantGreat:
2077        newWidth = 10;
2078      case VariantSuper:
2079        newHoldingsWidth = 2;
2080        gameInfo.holdingsSize = 8;
2081        break;
2082      case VariantGothic:
2083      case VariantCapablanca:
2084      case VariantCapaRandom:
2085        newWidth = 10;
2086      default:
2087        newHoldingsWidth = gameInfo.holdingsSize = 0;
2088      };
2089    
2090    if(newWidth  != gameInfo.boardWidth  ||
2091       newHeight != gameInfo.boardHeight ||
2092       newHoldingsWidth != gameInfo.holdingsWidth ) {
2093      
2094      /* shift position to new playing area, if needed */
2095      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2096        for(i=0; i<BOARD_HEIGHT; i++) 
2097          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2098            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2099              board[i][j];
2100        for(i=0; i<newHeight; i++) {
2101          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2102          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2103        }
2104      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2105        for(i=0; i<BOARD_HEIGHT; i++)
2106          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2107            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2108              board[i][j];
2109      }
2110      gameInfo.boardWidth  = newWidth;
2111      gameInfo.boardHeight = newHeight;
2112      gameInfo.holdingsWidth = newHoldingsWidth;
2113      gameInfo.variant = newVariant;
2114      InitDrawingSizes(-2, 0);
2115    } else gameInfo.variant = newVariant;
2116    CopyBoard(oldBoard, board);   // remember correctly formatted board
2117      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2118    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2119 }
2120
2121 static int loggedOn = FALSE;
2122
2123 /*-- Game start info cache: --*/
2124 int gs_gamenum;
2125 char gs_kind[MSG_SIZ];
2126 static char player1Name[128] = "";
2127 static char player2Name[128] = "";
2128 static char cont_seq[] = "\n\\   ";
2129 static int player1Rating = -1;
2130 static int player2Rating = -1;
2131 /*----------------------------*/
2132
2133 ColorClass curColor = ColorNormal;
2134 int suppressKibitz = 0;
2135
2136 // [HGM] seekgraph
2137 Boolean soughtPending = FALSE;
2138 Boolean seekGraphUp;
2139 #define MAX_SEEK_ADS 200
2140 #define SQUARE 0x80
2141 char *seekAdList[MAX_SEEK_ADS];
2142 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2143 float tcList[MAX_SEEK_ADS];
2144 char colorList[MAX_SEEK_ADS];
2145 int nrOfSeekAds = 0;
2146 int minRating = 1010, maxRating = 2800;
2147 int hMargin = 10, vMargin = 20, h, w;
2148 extern int squareSize, lineGap;
2149
2150 void
2151 PlotSeekAd(int i)
2152 {
2153         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2154         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2155         if(r < minRating+100 && r >=0 ) r = minRating+100;
2156         if(r > maxRating) r = maxRating;
2157         if(tc < 1.) tc = 1.;
2158         if(tc > 95.) tc = 95.;
2159         x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2160         y = ((double)r - minRating)/(maxRating - minRating)
2161             * (h-vMargin-squareSize/8-1) + vMargin;
2162         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2163         if(strstr(seekAdList[i], " u ")) color = 1;
2164         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2165            !strstr(seekAdList[i], "bullet") &&
2166            !strstr(seekAdList[i], "blitz") &&
2167            !strstr(seekAdList[i], "standard") ) color = 2;
2168         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2169         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2170 }
2171
2172 void
2173 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2174 {
2175         char buf[MSG_SIZ], *ext = "";
2176         VariantClass v = StringToVariant(type);
2177         if(strstr(type, "wild")) {
2178             ext = type + 4; // append wild number
2179             if(v == VariantFischeRandom) type = "chess960"; else
2180             if(v == VariantLoadable) type = "setup"; else
2181             type = VariantName(v);
2182         }
2183         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2184         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2185             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2186             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2187             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2188             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2189             seekNrList[nrOfSeekAds] = nr;
2190             zList[nrOfSeekAds] = 0;
2191             seekAdList[nrOfSeekAds++] = StrSave(buf);
2192             if(plot) PlotSeekAd(nrOfSeekAds-1);
2193         }
2194 }
2195
2196 void
2197 EraseSeekDot(int i)
2198 {
2199     int x = xList[i], y = yList[i], d=squareSize/4, k;
2200     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2201     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2202     // now replot every dot that overlapped
2203     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2204         int xx = xList[k], yy = yList[k];
2205         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2206             DrawSeekDot(xx, yy, colorList[k]);
2207     }
2208 }
2209
2210 void
2211 RemoveSeekAd(int nr)
2212 {
2213         int i;
2214         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2215             EraseSeekDot(i);
2216             if(seekAdList[i]) free(seekAdList[i]);
2217             seekAdList[i] = seekAdList[--nrOfSeekAds];
2218             seekNrList[i] = seekNrList[nrOfSeekAds];
2219             ratingList[i] = ratingList[nrOfSeekAds];
2220             colorList[i]  = colorList[nrOfSeekAds];
2221             tcList[i] = tcList[nrOfSeekAds];
2222             xList[i]  = xList[nrOfSeekAds];
2223             yList[i]  = yList[nrOfSeekAds];
2224             zList[i]  = zList[nrOfSeekAds];
2225             seekAdList[nrOfSeekAds] = NULL;
2226             break;
2227         }
2228 }
2229
2230 Boolean
2231 MatchSoughtLine(char *line)
2232 {
2233     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2234     int nr, base, inc, u=0; char dummy;
2235
2236     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2237        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2238        (u=1) &&
2239        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2240         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2241         // match: compact and save the line
2242         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2243         return TRUE;
2244     }
2245     return FALSE;
2246 }
2247
2248 int
2249 DrawSeekGraph()
2250 {
2251     if(!seekGraphUp) return FALSE;
2252     int i;
2253     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2254     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2255
2256     DrawSeekBackground(0, 0, w, h);
2257     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2258     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2259     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2260         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2261         yy = h-1-yy;
2262         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2263         if(i%500 == 0) {
2264             char buf[MSG_SIZ];
2265             sprintf(buf, "%d", i);
2266             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2267         }
2268     }
2269     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2270     for(i=1; i<100; i+=(i<10?1:5)) {
2271         int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2272         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2273         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2274             char buf[MSG_SIZ];
2275             sprintf(buf, "%d", i);
2276             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2277         }
2278     }
2279     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2280     return TRUE;
2281 }
2282
2283 int SeekGraphClick(ClickType click, int x, int y, int moving)
2284 {
2285     static int lastDown = 0, displayed = 0, lastSecond;
2286     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2287         if(click == Release || moving) return FALSE;
2288         nrOfSeekAds = 0;
2289         soughtPending = TRUE;
2290         SendToICS(ics_prefix);
2291         SendToICS("sought\n"); // should this be "sought all"?
2292     } else { // issue challenge based on clicked ad
2293         int dist = 10000; int i, closest = 0, second = 0;
2294         for(i=0; i<nrOfSeekAds; i++) {
2295             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2296             if(d < dist) { dist = d; closest = i; }
2297             second += (d - zList[i] < 120); // count in-range ads
2298             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2299         }
2300         if(dist < 120) {
2301             char buf[MSG_SIZ];
2302             second = (second > 1);
2303             if(displayed != closest || second != lastSecond) {
2304                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2305                 lastSecond = second; displayed = closest;
2306             }
2307             sprintf(buf, "play %d\n", seekNrList[closest]);
2308             if(click == Press) {
2309                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2310                 lastDown = closest;
2311                 return TRUE;
2312             } // on press 'hit', only show info
2313             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2314             SendToICS(ics_prefix);
2315             SendToICS(buf); // should this be "sought all"?
2316         } else if(click == Release) { // release 'miss' is ignored
2317             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2318             if(moving == 2) { // right up-click
2319                 nrOfSeekAds = 0; // refresh graph
2320                 soughtPending = TRUE;
2321                 SendToICS(ics_prefix);
2322                 SendToICS("sought\n"); // should this be "sought all"?
2323             }
2324             return TRUE;
2325         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2326         // press miss or release hit 'pop down' seek graph
2327         seekGraphUp = FALSE;
2328         DrawPosition(TRUE, NULL);
2329     }
2330     return TRUE;
2331 }
2332
2333 void
2334 read_from_ics(isr, closure, data, count, error)
2335      InputSourceRef isr;
2336      VOIDSTAR closure;
2337      char *data;
2338      int count;
2339      int error;
2340 {
2341 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2342 #define STARTED_NONE 0
2343 #define STARTED_MOVES 1
2344 #define STARTED_BOARD 2
2345 #define STARTED_OBSERVE 3
2346 #define STARTED_HOLDINGS 4
2347 #define STARTED_CHATTER 5
2348 #define STARTED_COMMENT 6
2349 #define STARTED_MOVES_NOHIDE 7
2350
2351     static int started = STARTED_NONE;
2352     static char parse[20000];
2353     static int parse_pos = 0;
2354     static char buf[BUF_SIZE + 1];
2355     static int firstTime = TRUE, intfSet = FALSE;
2356     static ColorClass prevColor = ColorNormal;
2357     static int savingComment = FALSE;
2358     static int cmatch = 0; // continuation sequence match
2359     char *bp;
2360     char str[500];
2361     int i, oldi;
2362     int buf_len;
2363     int next_out;
2364     int tkind;
2365     int backup;    /* [DM] For zippy color lines */
2366     char *p;
2367     char talker[MSG_SIZ]; // [HGM] chat
2368     int channel;
2369
2370     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2371
2372     if (appData.debugMode) {
2373       if (!error) {
2374         fprintf(debugFP, "<ICS: ");
2375         show_bytes(debugFP, data, count);
2376         fprintf(debugFP, "\n");
2377       }
2378     }
2379
2380     if (appData.debugMode) { int f = forwardMostMove;
2381         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2382                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2383                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2384     }
2385     if (count > 0) {
2386         /* If last read ended with a partial line that we couldn't parse,
2387            prepend it to the new read and try again. */
2388         if (leftover_len > 0) {
2389             for (i=0; i<leftover_len; i++)
2390               buf[i] = buf[leftover_start + i];
2391         }
2392
2393     /* copy new characters into the buffer */
2394     bp = buf + leftover_len;
2395     buf_len=leftover_len;
2396     for (i=0; i<count; i++)
2397     {
2398         // ignore these
2399         if (data[i] == '\r')
2400             continue;
2401
2402         // join lines split by ICS?
2403         if (!appData.noJoin)
2404         {
2405             /*
2406                 Joining just consists of finding matches against the
2407                 continuation sequence, and discarding that sequence
2408                 if found instead of copying it.  So, until a match
2409                 fails, there's nothing to do since it might be the
2410                 complete sequence, and thus, something we don't want
2411                 copied.
2412             */
2413             if (data[i] == cont_seq[cmatch])
2414             {
2415                 cmatch++;
2416                 if (cmatch == strlen(cont_seq))
2417                 {
2418                     cmatch = 0; // complete match.  just reset the counter
2419
2420                     /*
2421                         it's possible for the ICS to not include the space
2422                         at the end of the last word, making our [correct]
2423                         join operation fuse two separate words.  the server
2424                         does this when the space occurs at the width setting.
2425                     */
2426                     if (!buf_len || buf[buf_len-1] != ' ')
2427                     {
2428                         *bp++ = ' ';
2429                         buf_len++;
2430                     }
2431                 }
2432                 continue;
2433             }
2434             else if (cmatch)
2435             {
2436                 /*
2437                     match failed, so we have to copy what matched before
2438                     falling through and copying this character.  In reality,
2439                     this will only ever be just the newline character, but
2440                     it doesn't hurt to be precise.
2441                 */
2442                 strncpy(bp, cont_seq, cmatch);
2443                 bp += cmatch;
2444                 buf_len += cmatch;
2445                 cmatch = 0;
2446             }
2447         }
2448
2449         // copy this char
2450         *bp++ = data[i];
2451         buf_len++;
2452     }
2453
2454         buf[buf_len] = NULLCHAR;
2455 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2456         next_out = 0;
2457         leftover_start = 0;
2458
2459         i = 0;
2460         while (i < buf_len) {
2461             /* Deal with part of the TELNET option negotiation
2462                protocol.  We refuse to do anything beyond the
2463                defaults, except that we allow the WILL ECHO option,
2464                which ICS uses to turn off password echoing when we are
2465                directly connected to it.  We reject this option
2466                if localLineEditing mode is on (always on in xboard)
2467                and we are talking to port 23, which might be a real
2468                telnet server that will try to keep WILL ECHO on permanently.
2469              */
2470             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2471                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2472                 unsigned char option;
2473                 oldi = i;
2474                 switch ((unsigned char) buf[++i]) {
2475                   case TN_WILL:
2476                     if (appData.debugMode)
2477                       fprintf(debugFP, "\n<WILL ");
2478                     switch (option = (unsigned char) buf[++i]) {
2479                       case TN_ECHO:
2480                         if (appData.debugMode)
2481                           fprintf(debugFP, "ECHO ");
2482                         /* Reply only if this is a change, according
2483                            to the protocol rules. */
2484                         if (remoteEchoOption) break;
2485                         if (appData.localLineEditing &&
2486                             atoi(appData.icsPort) == TN_PORT) {
2487                             TelnetRequest(TN_DONT, TN_ECHO);
2488                         } else {
2489                             EchoOff();
2490                             TelnetRequest(TN_DO, TN_ECHO);
2491                             remoteEchoOption = TRUE;
2492                         }
2493                         break;
2494                       default:
2495                         if (appData.debugMode)
2496                           fprintf(debugFP, "%d ", option);
2497                         /* Whatever this is, we don't want it. */
2498                         TelnetRequest(TN_DONT, option);
2499                         break;
2500                     }
2501                     break;
2502                   case TN_WONT:
2503                     if (appData.debugMode)
2504                       fprintf(debugFP, "\n<WONT ");
2505                     switch (option = (unsigned char) buf[++i]) {
2506                       case TN_ECHO:
2507                         if (appData.debugMode)
2508                           fprintf(debugFP, "ECHO ");
2509                         /* Reply only if this is a change, according
2510                            to the protocol rules. */
2511                         if (!remoteEchoOption) break;
2512                         EchoOn();
2513                         TelnetRequest(TN_DONT, TN_ECHO);
2514                         remoteEchoOption = FALSE;
2515                         break;
2516                       default:
2517                         if (appData.debugMode)
2518                           fprintf(debugFP, "%d ", (unsigned char) option);
2519                         /* Whatever this is, it must already be turned
2520                            off, because we never agree to turn on
2521                            anything non-default, so according to the
2522                            protocol rules, we don't reply. */
2523                         break;
2524                     }
2525                     break;
2526                   case TN_DO:
2527                     if (appData.debugMode)
2528                       fprintf(debugFP, "\n<DO ");
2529                     switch (option = (unsigned char) buf[++i]) {
2530                       default:
2531                         /* Whatever this is, we refuse to do it. */
2532                         if (appData.debugMode)
2533                           fprintf(debugFP, "%d ", option);
2534                         TelnetRequest(TN_WONT, option);
2535                         break;
2536                     }
2537                     break;
2538                   case TN_DONT:
2539                     if (appData.debugMode)
2540                       fprintf(debugFP, "\n<DONT ");
2541                     switch (option = (unsigned char) buf[++i]) {
2542                       default:
2543                         if (appData.debugMode)
2544                           fprintf(debugFP, "%d ", option);
2545                         /* Whatever this is, we are already not doing
2546                            it, because we never agree to do anything
2547                            non-default, so according to the protocol
2548                            rules, we don't reply. */
2549                         break;
2550                     }
2551                     break;
2552                   case TN_IAC:
2553                     if (appData.debugMode)
2554                       fprintf(debugFP, "\n<IAC ");
2555                     /* Doubled IAC; pass it through */
2556                     i--;
2557                     break;
2558                   default:
2559                     if (appData.debugMode)
2560                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2561                     /* Drop all other telnet commands on the floor */
2562                     break;
2563                 }
2564                 if (oldi > next_out)
2565                   SendToPlayer(&buf[next_out], oldi - next_out);
2566                 if (++i > next_out)
2567                   next_out = i;
2568                 continue;
2569             }
2570
2571             /* OK, this at least will *usually* work */
2572             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2573                 loggedOn = TRUE;
2574             }
2575
2576             if (loggedOn && !intfSet) {
2577                 if (ics_type == ICS_ICC) {
2578                   sprintf(str,
2579                           "/set-quietly interface %s\n/set-quietly style 12\n",
2580                           programVersion);
2581                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2582                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2583                 } else if (ics_type == ICS_CHESSNET) {
2584                   sprintf(str, "/style 12\n");
2585                 } else {
2586                   strcpy(str, "alias $ @\n$set interface ");
2587                   strcat(str, programVersion);
2588                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2589                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2590                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2591 #ifdef WIN32
2592                   strcat(str, "$iset nohighlight 1\n");
2593 #endif
2594                   strcat(str, "$iset lock 1\n$style 12\n");
2595                 }
2596                 SendToICS(str);
2597                 NotifyFrontendLogin();
2598                 intfSet = TRUE;
2599             }
2600
2601             if (started == STARTED_COMMENT) {
2602                 /* Accumulate characters in comment */
2603                 parse[parse_pos++] = buf[i];
2604                 if (buf[i] == '\n') {
2605                     parse[parse_pos] = NULLCHAR;
2606                     if(chattingPartner>=0) {
2607                         char mess[MSG_SIZ];
2608                         sprintf(mess, "%s%s", talker, parse);
2609                         OutputChatMessage(chattingPartner, mess);
2610                         chattingPartner = -1;
2611                     } else
2612                     if(!suppressKibitz) // [HGM] kibitz
2613                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2614                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2615                         int nrDigit = 0, nrAlph = 0, j;
2616                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2617                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2618                         parse[parse_pos] = NULLCHAR;
2619                         // try to be smart: if it does not look like search info, it should go to
2620                         // ICS interaction window after all, not to engine-output window.
2621                         for(j=0; j<parse_pos; j++) { // count letters and digits
2622                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2623                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2624                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2625                         }
2626                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2627                             int depth=0; float score;
2628                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2629                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2630                                 pvInfoList[forwardMostMove-1].depth = depth;
2631                                 pvInfoList[forwardMostMove-1].score = 100*score;
2632                             }
2633                             OutputKibitz(suppressKibitz, parse);
2634                             next_out = i+1; // [HGM] suppress printing in ICS window
2635                         } else {
2636                             char tmp[MSG_SIZ];
2637                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2638                             SendToPlayer(tmp, strlen(tmp));
2639                         }
2640                     }
2641                     started = STARTED_NONE;
2642                 } else {
2643                     /* Don't match patterns against characters in comment */
2644                     i++;
2645                     continue;
2646                 }
2647             }
2648             if (started == STARTED_CHATTER) {
2649                 if (buf[i] != '\n') {
2650                     /* Don't match patterns against characters in chatter */
2651                     i++;
2652                     continue;
2653                 }
2654                 started = STARTED_NONE;
2655             }
2656
2657             /* Kludge to deal with rcmd protocol */
2658             if (firstTime && looking_at(buf, &i, "\001*")) {
2659                 DisplayFatalError(&buf[1], 0, 1);
2660                 continue;
2661             } else {
2662                 firstTime = FALSE;
2663             }
2664
2665             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2666                 ics_type = ICS_ICC;
2667                 ics_prefix = "/";
2668                 if (appData.debugMode)
2669                   fprintf(debugFP, "ics_type %d\n", ics_type);
2670                 continue;
2671             }
2672             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2673                 ics_type = ICS_FICS;
2674                 ics_prefix = "$";
2675                 if (appData.debugMode)
2676                   fprintf(debugFP, "ics_type %d\n", ics_type);
2677                 continue;
2678             }
2679             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2680                 ics_type = ICS_CHESSNET;
2681                 ics_prefix = "/";
2682                 if (appData.debugMode)
2683                   fprintf(debugFP, "ics_type %d\n", ics_type);
2684                 continue;
2685             }
2686
2687             if (!loggedOn &&
2688                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2689                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2690                  looking_at(buf, &i, "will be \"*\""))) {
2691               strcpy(ics_handle, star_match[0]);
2692               continue;
2693             }
2694
2695             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2696               char buf[MSG_SIZ];
2697               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2698               DisplayIcsInteractionTitle(buf);
2699               have_set_title = TRUE;
2700             }
2701
2702             /* skip finger notes */
2703             if (started == STARTED_NONE &&
2704                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2705                  (buf[i] == '1' && buf[i+1] == '0')) &&
2706                 buf[i+2] == ':' && buf[i+3] == ' ') {
2707               started = STARTED_CHATTER;
2708               i += 3;
2709               continue;
2710             }
2711
2712             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2713             if(appData.seekGraph) {
2714                 if(soughtPending && MatchSoughtLine(buf+i)) {
2715                     i = strstr(buf+i, "rated") - buf;
2716                     next_out = leftover_start = i;
2717                     started = STARTED_CHATTER;
2718                     suppressKibitz = TRUE;
2719                     continue;
2720                 }
2721                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2722                         && looking_at(buf, &i, "* ads displayed")) {
2723                     soughtPending = FALSE;
2724                     seekGraphUp = TRUE;
2725                     DrawSeekGraph();
2726                     continue;
2727                 }
2728                 if(appData.autoRefresh) {
2729                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2730                         int s = (ics_type == ICS_ICC); // ICC format differs
2731                         if(seekGraphUp)
2732                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2733                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2734                         looking_at(buf, &i, "*% "); // eat prompt
2735                         next_out = i; // suppress
2736                         continue;
2737                     }
2738                     if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2739                         char *p = star_match[0];
2740                         while(*p) {
2741                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2742                             while(*p && *p++ != ' '); // next
2743                         }
2744                         looking_at(buf, &i, "*% "); // eat prompt
2745                         next_out = i;
2746                         continue;
2747                     }
2748                 }
2749             }
2750
2751             /* skip formula vars */
2752             if (started == STARTED_NONE &&
2753                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2754               started = STARTED_CHATTER;
2755               i += 3;
2756               continue;
2757             }
2758
2759             oldi = i;
2760             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2761             if (appData.autoKibitz && started == STARTED_NONE &&
2762                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2763                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2764                 if(looking_at(buf, &i, "* kibitzes: ") &&
2765                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2766                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2767                         suppressKibitz = TRUE;
2768                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2769                                 && (gameMode == IcsPlayingWhite)) ||
2770                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2771                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2772                             started = STARTED_CHATTER; // own kibitz we simply discard
2773                         else {
2774                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2775                             parse_pos = 0; parse[0] = NULLCHAR;
2776                             savingComment = TRUE;
2777                             suppressKibitz = gameMode != IcsObserving ? 2 :
2778                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2779                         }
2780                         continue;
2781                 } else
2782                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2783                     // suppress the acknowledgements of our own autoKibitz
2784                     char *p;
2785                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2786                     SendToPlayer(star_match[0], strlen(star_match[0]));
2787                     looking_at(buf, &i, "*% "); // eat prompt
2788                     next_out = i;
2789                 }
2790             } // [HGM] kibitz: end of patch
2791
2792 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2793
2794             // [HGM] chat: intercept tells by users for which we have an open chat window
2795             channel = -1;
2796             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2797                                            looking_at(buf, &i, "* whispers:") ||
2798                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2799                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2800                 int p;
2801                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2802                 chattingPartner = -1;
2803
2804                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2805                 for(p=0; p<MAX_CHAT; p++) {
2806                     if(channel == atoi(chatPartner[p])) {
2807                     talker[0] = '['; strcat(talker, "] ");
2808                     chattingPartner = p; break;
2809                     }
2810                 } else
2811                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2812                 for(p=0; p<MAX_CHAT; p++) {
2813                     if(!strcmp("WHISPER", chatPartner[p])) {
2814                         talker[0] = '['; strcat(talker, "] ");
2815                         chattingPartner = p; break;
2816                     }
2817                 }
2818                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2819                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2820                     talker[0] = 0;
2821                     chattingPartner = p; break;
2822                 }
2823                 if(chattingPartner<0) i = oldi; else {
2824                     started = STARTED_COMMENT;
2825                     parse_pos = 0; parse[0] = NULLCHAR;
2826                     savingComment = 3 + chattingPartner; // counts as TRUE
2827                     suppressKibitz = TRUE;
2828                 }
2829             } // [HGM] chat: end of patch
2830
2831             if (appData.zippyTalk || appData.zippyPlay) {
2832                 /* [DM] Backup address for color zippy lines */
2833                 backup = i;
2834 #if ZIPPY
2835        #ifdef WIN32
2836                if (loggedOn == TRUE)
2837                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2838                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2839        #else
2840                 if (ZippyControl(buf, &i) ||
2841                     ZippyConverse(buf, &i) ||
2842                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2843                       loggedOn = TRUE;
2844                       if (!appData.colorize) continue;
2845                 }
2846        #endif
2847 #endif
2848             } // [DM] 'else { ' deleted
2849                 if (
2850                     /* Regular tells and says */
2851                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2852                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2853                     looking_at(buf, &i, "* says: ") ||
2854                     /* Don't color "message" or "messages" output */
2855                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2856                     looking_at(buf, &i, "*. * at *:*: ") ||
2857                     looking_at(buf, &i, "--* (*:*): ") ||
2858                     /* Message notifications (same color as tells) */
2859                     looking_at(buf, &i, "* has left a message ") ||
2860                     looking_at(buf, &i, "* just sent you a message:\n") ||
2861                     /* Whispers and kibitzes */
2862                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2863                     looking_at(buf, &i, "* kibitzes: ") ||
2864                     /* Channel tells */
2865                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2866
2867                   if (tkind == 1 && strchr(star_match[0], ':')) {
2868                       /* Avoid "tells you:" spoofs in channels */
2869                      tkind = 3;
2870                   }
2871                   if (star_match[0][0] == NULLCHAR ||
2872                       strchr(star_match[0], ' ') ||
2873                       (tkind == 3 && strchr(star_match[1], ' '))) {
2874                     /* Reject bogus matches */
2875                     i = oldi;
2876                   } else {
2877                     if (appData.colorize) {
2878                       if (oldi > next_out) {
2879                         SendToPlayer(&buf[next_out], oldi - next_out);
2880                         next_out = oldi;
2881                       }
2882                       switch (tkind) {
2883                       case 1:
2884                         Colorize(ColorTell, FALSE);
2885                         curColor = ColorTell;
2886                         break;
2887                       case 2:
2888                         Colorize(ColorKibitz, FALSE);
2889                         curColor = ColorKibitz;
2890                         break;
2891                       case 3:
2892                         p = strrchr(star_match[1], '(');
2893                         if (p == NULL) {
2894                           p = star_match[1];
2895                         } else {
2896                           p++;
2897                         }
2898                         if (atoi(p) == 1) {
2899                           Colorize(ColorChannel1, FALSE);
2900                           curColor = ColorChannel1;
2901                         } else {
2902                           Colorize(ColorChannel, FALSE);
2903                           curColor = ColorChannel;
2904                         }
2905                         break;
2906                       case 5:
2907                         curColor = ColorNormal;
2908                         break;
2909                       }
2910                     }
2911                     if (started == STARTED_NONE && appData.autoComment &&
2912                         (gameMode == IcsObserving ||
2913                          gameMode == IcsPlayingWhite ||
2914                          gameMode == IcsPlayingBlack)) {
2915                       parse_pos = i - oldi;
2916                       memcpy(parse, &buf[oldi], parse_pos);
2917                       parse[parse_pos] = NULLCHAR;
2918                       started = STARTED_COMMENT;
2919                       savingComment = TRUE;
2920                     } else {
2921                       started = STARTED_CHATTER;
2922                       savingComment = FALSE;
2923                     }
2924                     loggedOn = TRUE;
2925                     continue;
2926                   }
2927                 }
2928
2929                 if (looking_at(buf, &i, "* s-shouts: ") ||
2930                     looking_at(buf, &i, "* c-shouts: ")) {
2931                     if (appData.colorize) {
2932                         if (oldi > next_out) {
2933                             SendToPlayer(&buf[next_out], oldi - next_out);
2934                             next_out = oldi;
2935                         }
2936                         Colorize(ColorSShout, FALSE);
2937                         curColor = ColorSShout;
2938                     }
2939                     loggedOn = TRUE;
2940                     started = STARTED_CHATTER;
2941                     continue;
2942                 }
2943
2944                 if (looking_at(buf, &i, "--->")) {
2945                     loggedOn = TRUE;
2946                     continue;
2947                 }
2948
2949                 if (looking_at(buf, &i, "* shouts: ") ||
2950                     looking_at(buf, &i, "--> ")) {
2951                     if (appData.colorize) {
2952                         if (oldi > next_out) {
2953                             SendToPlayer(&buf[next_out], oldi - next_out);
2954                             next_out = oldi;
2955                         }
2956                         Colorize(ColorShout, FALSE);
2957                         curColor = ColorShout;
2958                     }
2959                     loggedOn = TRUE;
2960                     started = STARTED_CHATTER;
2961                     continue;
2962                 }
2963
2964                 if (looking_at( buf, &i, "Challenge:")) {
2965                     if (appData.colorize) {
2966                         if (oldi > next_out) {
2967                             SendToPlayer(&buf[next_out], oldi - next_out);
2968                             next_out = oldi;
2969                         }
2970                         Colorize(ColorChallenge, FALSE);
2971                         curColor = ColorChallenge;
2972                     }
2973                     loggedOn = TRUE;
2974                     continue;
2975                 }
2976
2977                 if (looking_at(buf, &i, "* offers you") ||
2978                     looking_at(buf, &i, "* offers to be") ||
2979                     looking_at(buf, &i, "* would like to") ||
2980                     looking_at(buf, &i, "* requests to") ||
2981                     looking_at(buf, &i, "Your opponent offers") ||
2982                     looking_at(buf, &i, "Your opponent requests")) {
2983
2984                     if (appData.colorize) {
2985                         if (oldi > next_out) {
2986                             SendToPlayer(&buf[next_out], oldi - next_out);
2987                             next_out = oldi;
2988                         }
2989                         Colorize(ColorRequest, FALSE);
2990                         curColor = ColorRequest;
2991                     }
2992                     continue;
2993                 }
2994
2995                 if (looking_at(buf, &i, "* (*) seeking")) {
2996                     if (appData.colorize) {
2997                         if (oldi > next_out) {
2998                             SendToPlayer(&buf[next_out], oldi - next_out);
2999                             next_out = oldi;
3000                         }
3001                         Colorize(ColorSeek, FALSE);
3002                         curColor = ColorSeek;
3003                     }
3004                     continue;
3005             }
3006
3007             if (looking_at(buf, &i, "\\   ")) {
3008                 if (prevColor != ColorNormal) {
3009                     if (oldi > next_out) {
3010                         SendToPlayer(&buf[next_out], oldi - next_out);
3011                         next_out = oldi;
3012                     }
3013                     Colorize(prevColor, TRUE);
3014                     curColor = prevColor;
3015                 }
3016                 if (savingComment) {
3017                     parse_pos = i - oldi;
3018                     memcpy(parse, &buf[oldi], parse_pos);
3019                     parse[parse_pos] = NULLCHAR;
3020                     started = STARTED_COMMENT;
3021                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3022                         chattingPartner = savingComment - 3; // kludge to remember the box
3023                 } else {
3024                     started = STARTED_CHATTER;
3025                 }
3026                 continue;
3027             }
3028
3029             if (looking_at(buf, &i, "Black Strength :") ||
3030                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3031                 looking_at(buf, &i, "<10>") ||
3032                 looking_at(buf, &i, "#@#")) {
3033                 /* Wrong board style */
3034                 loggedOn = TRUE;
3035                 SendToICS(ics_prefix);
3036                 SendToICS("set style 12\n");
3037                 SendToICS(ics_prefix);
3038                 SendToICS("refresh\n");
3039                 continue;
3040             }
3041
3042             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3043                 ICSInitScript();
3044                 have_sent_ICS_logon = 1;
3045                 continue;
3046             }
3047
3048             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3049                 (looking_at(buf, &i, "\n<12> ") ||
3050                  looking_at(buf, &i, "<12> "))) {
3051                 loggedOn = TRUE;
3052                 if (oldi > next_out) {
3053                     SendToPlayer(&buf[next_out], oldi - next_out);
3054                 }
3055                 next_out = i;
3056                 started = STARTED_BOARD;
3057                 parse_pos = 0;
3058                 continue;
3059             }
3060
3061             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3062                 looking_at(buf, &i, "<b1> ")) {
3063                 if (oldi > next_out) {
3064                     SendToPlayer(&buf[next_out], oldi - next_out);
3065                 }
3066                 next_out = i;
3067                 started = STARTED_HOLDINGS;
3068                 parse_pos = 0;
3069                 continue;
3070             }
3071
3072             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3073                 loggedOn = TRUE;
3074                 /* Header for a move list -- first line */
3075
3076                 switch (ics_getting_history) {
3077                   case H_FALSE:
3078                     switch (gameMode) {
3079                       case IcsIdle:
3080                       case BeginningOfGame:
3081                         /* User typed "moves" or "oldmoves" while we
3082                            were idle.  Pretend we asked for these
3083                            moves and soak them up so user can step
3084                            through them and/or save them.
3085                            */
3086                         Reset(FALSE, TRUE);
3087                         gameMode = IcsObserving;
3088                         ModeHighlight();
3089                         ics_gamenum = -1;
3090                         ics_getting_history = H_GOT_UNREQ_HEADER;
3091                         break;
3092                       case EditGame: /*?*/
3093                       case EditPosition: /*?*/
3094                         /* Should above feature work in these modes too? */
3095                         /* For now it doesn't */
3096                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3097                         break;
3098                       default:
3099                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3100                         break;
3101                     }
3102                     break;
3103                   case H_REQUESTED:
3104                     /* Is this the right one? */
3105                     if (gameInfo.white && gameInfo.black &&
3106                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3107                         strcmp(gameInfo.black, star_match[2]) == 0) {
3108                         /* All is well */
3109                         ics_getting_history = H_GOT_REQ_HEADER;
3110                     }
3111                     break;
3112                   case H_GOT_REQ_HEADER:
3113                   case H_GOT_UNREQ_HEADER:
3114                   case H_GOT_UNWANTED_HEADER:
3115                   case H_GETTING_MOVES:
3116                     /* Should not happen */
3117                     DisplayError(_("Error gathering move list: two headers"), 0);
3118                     ics_getting_history = H_FALSE;
3119                     break;
3120                 }
3121
3122                 /* Save player ratings into gameInfo if needed */
3123                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3124                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3125                     (gameInfo.whiteRating == -1 ||
3126                      gameInfo.blackRating == -1)) {
3127
3128                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3129                     gameInfo.blackRating = string_to_rating(star_match[3]);
3130                     if (appData.debugMode)
3131                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3132                               gameInfo.whiteRating, gameInfo.blackRating);
3133                 }
3134                 continue;
3135             }
3136
3137             if (looking_at(buf, &i,
3138               "* * match, initial time: * minute*, increment: * second")) {
3139                 /* Header for a move list -- second line */
3140                 /* Initial board will follow if this is a wild game */
3141                 if (gameInfo.event != NULL) free(gameInfo.event);
3142                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3143                 gameInfo.event = StrSave(str);
3144                 /* [HGM] we switched variant. Translate boards if needed. */
3145                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3146                 continue;
3147             }
3148
3149             if (looking_at(buf, &i, "Move  ")) {
3150                 /* Beginning of a move list */
3151                 switch (ics_getting_history) {
3152                   case H_FALSE:
3153                     /* Normally should not happen */
3154                     /* Maybe user hit reset while we were parsing */
3155                     break;
3156                   case H_REQUESTED:
3157                     /* Happens if we are ignoring a move list that is not
3158                      * the one we just requested.  Common if the user
3159                      * tries to observe two games without turning off
3160                      * getMoveList */
3161                     break;
3162                   case H_GETTING_MOVES:
3163                     /* Should not happen */
3164                     DisplayError(_("Error gathering move list: nested"), 0);
3165                     ics_getting_history = H_FALSE;
3166                     break;
3167                   case H_GOT_REQ_HEADER:
3168                     ics_getting_history = H_GETTING_MOVES;
3169                     started = STARTED_MOVES;
3170                     parse_pos = 0;
3171                     if (oldi > next_out) {
3172                         SendToPlayer(&buf[next_out], oldi - next_out);
3173                     }
3174                     break;
3175                   case H_GOT_UNREQ_HEADER:
3176                     ics_getting_history = H_GETTING_MOVES;
3177                     started = STARTED_MOVES_NOHIDE;
3178                     parse_pos = 0;
3179                     break;
3180                   case H_GOT_UNWANTED_HEADER:
3181                     ics_getting_history = H_FALSE;
3182                     break;
3183                 }
3184                 continue;
3185             }
3186
3187             if (looking_at(buf, &i, "% ") ||
3188                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3189                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3190                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3191                     soughtPending = FALSE;
3192                     seekGraphUp = TRUE;
3193                     DrawSeekGraph();
3194                 }
3195                 if(suppressKibitz) next_out = i;
3196                 savingComment = FALSE;
3197                 suppressKibitz = 0;
3198                 switch (started) {
3199                   case STARTED_MOVES:
3200                   case STARTED_MOVES_NOHIDE:
3201                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3202                     parse[parse_pos + i - oldi] = NULLCHAR;
3203                     ParseGameHistory(parse);
3204 #if ZIPPY
3205                     if (appData.zippyPlay && first.initDone) {
3206                         FeedMovesToProgram(&first, forwardMostMove);
3207                         if (gameMode == IcsPlayingWhite) {
3208                             if (WhiteOnMove(forwardMostMove)) {
3209                                 if (first.sendTime) {
3210                                   if (first.useColors) {
3211                                     SendToProgram("black\n", &first);
3212                                   }
3213                                   SendTimeRemaining(&first, TRUE);
3214                                 }
3215                                 if (first.useColors) {
3216                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3217                                 }
3218                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3219                                 first.maybeThinking = TRUE;
3220                             } else {
3221                                 if (first.usePlayother) {
3222                                   if (first.sendTime) {
3223                                     SendTimeRemaining(&first, TRUE);
3224                                   }
3225                                   SendToProgram("playother\n", &first);
3226                                   firstMove = FALSE;
3227                                 } else {
3228                                   firstMove = TRUE;
3229                                 }
3230                             }
3231                         } else if (gameMode == IcsPlayingBlack) {
3232                             if (!WhiteOnMove(forwardMostMove)) {
3233                                 if (first.sendTime) {
3234                                   if (first.useColors) {
3235                                     SendToProgram("white\n", &first);
3236                                   }
3237                                   SendTimeRemaining(&first, FALSE);
3238                                 }
3239                                 if (first.useColors) {
3240                                   SendToProgram("black\n", &first);
3241                                 }
3242                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3243                                 first.maybeThinking = TRUE;
3244                             } else {
3245                                 if (first.usePlayother) {
3246                                   if (first.sendTime) {
3247                                     SendTimeRemaining(&first, FALSE);
3248                                   }
3249                                   SendToProgram("playother\n", &first);
3250                                   firstMove = FALSE;
3251                                 } else {
3252                                   firstMove = TRUE;
3253                                 }
3254                             }
3255                         }
3256                     }
3257 #endif
3258                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3259                         /* Moves came from oldmoves or moves command
3260                            while we weren't doing anything else.
3261                            */
3262                         currentMove = forwardMostMove;
3263                         ClearHighlights();/*!!could figure this out*/
3264                         flipView = appData.flipView;
3265                         DrawPosition(TRUE, boards[currentMove]);
3266                         DisplayBothClocks();
3267                         sprintf(str, "%s vs. %s",
3268                                 gameInfo.white, gameInfo.black);
3269                         DisplayTitle(str);
3270                         gameMode = IcsIdle;
3271                     } else {
3272                         /* Moves were history of an active game */
3273                         if (gameInfo.resultDetails != NULL) {
3274                             free(gameInfo.resultDetails);
3275                             gameInfo.resultDetails = NULL;
3276                         }
3277                     }
3278                     HistorySet(parseList, backwardMostMove,
3279                                forwardMostMove, currentMove-1);
3280                     DisplayMove(currentMove - 1);
3281                     if (started == STARTED_MOVES) next_out = i;
3282                     started = STARTED_NONE;
3283                     ics_getting_history = H_FALSE;
3284                     break;
3285
3286                   case STARTED_OBSERVE:
3287                     started = STARTED_NONE;
3288                     SendToICS(ics_prefix);
3289                     SendToICS("refresh\n");
3290                     break;
3291
3292                   default:
3293                     break;
3294                 }
3295                 if(bookHit) { // [HGM] book: simulate book reply
3296                     static char bookMove[MSG_SIZ]; // a bit generous?
3297
3298                     programStats.nodes = programStats.depth = programStats.time =
3299                     programStats.score = programStats.got_only_move = 0;
3300                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3301
3302                     strcpy(bookMove, "move ");
3303                     strcat(bookMove, bookHit);
3304                     HandleMachineMove(bookMove, &first);
3305                 }
3306                 continue;
3307             }
3308
3309             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3310                  started == STARTED_HOLDINGS ||
3311                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3312                 /* Accumulate characters in move list or board */
3313                 parse[parse_pos++] = buf[i];
3314             }
3315
3316             /* Start of game messages.  Mostly we detect start of game
3317                when the first board image arrives.  On some versions
3318                of the ICS, though, we need to do a "refresh" after starting
3319                to observe in order to get the current board right away. */
3320             if (looking_at(buf, &i, "Adding game * to observation list")) {
3321                 started = STARTED_OBSERVE;
3322                 continue;
3323             }
3324
3325             /* Handle auto-observe */
3326             if (appData.autoObserve &&
3327                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3328                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3329                 char *player;
3330                 /* Choose the player that was highlighted, if any. */
3331                 if (star_match[0][0] == '\033' ||
3332                     star_match[1][0] != '\033') {
3333                     player = star_match[0];
3334                 } else {
3335                     player = star_match[2];
3336                 }
3337                 sprintf(str, "%sobserve %s\n",
3338                         ics_prefix, StripHighlightAndTitle(player));
3339                 SendToICS(str);
3340
3341                 /* Save ratings from notify string */
3342                 strcpy(player1Name, star_match[0]);
3343                 player1Rating = string_to_rating(star_match[1]);
3344                 strcpy(player2Name, star_match[2]);
3345                 player2Rating = string_to_rating(star_match[3]);
3346
3347                 if (appData.debugMode)
3348                   fprintf(debugFP,
3349                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3350                           player1Name, player1Rating,
3351                           player2Name, player2Rating);
3352
3353                 continue;
3354             }
3355
3356             /* Deal with automatic examine mode after a game,
3357                and with IcsObserving -> IcsExamining transition */
3358             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3359                 looking_at(buf, &i, "has made you an examiner of game *")) {
3360
3361                 int gamenum = atoi(star_match[0]);
3362                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3363                     gamenum == ics_gamenum) {
3364                     /* We were already playing or observing this game;
3365                        no need to refetch history */
3366                     gameMode = IcsExamining;
3367                     if (pausing) {
3368                         pauseExamForwardMostMove = forwardMostMove;
3369                     } else if (currentMove < forwardMostMove) {
3370                         ForwardInner(forwardMostMove);
3371                     }
3372                 } else {
3373                     /* I don't think this case really can happen */
3374                     SendToICS(ics_prefix);
3375                     SendToICS("refresh\n");
3376                 }
3377                 continue;
3378             }
3379
3380             /* Error messages */
3381 //          if (ics_user_moved) {
3382             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3383                 if (looking_at(buf, &i, "Illegal move") ||
3384                     looking_at(buf, &i, "Not a legal move") ||
3385                     looking_at(buf, &i, "Your king is in check") ||
3386                     looking_at(buf, &i, "It isn't your turn") ||
3387                     looking_at(buf, &i, "It is not your move")) {
3388                     /* Illegal move */
3389                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3390                         currentMove = --forwardMostMove;
3391                         DisplayMove(currentMove - 1); /* before DMError */
3392                         DrawPosition(FALSE, boards[currentMove]);
3393                         SwitchClocks();
3394                         DisplayBothClocks();
3395                     }
3396                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3397                     ics_user_moved = 0;
3398                     continue;
3399                 }
3400             }
3401
3402             if (looking_at(buf, &i, "still have time") ||
3403                 looking_at(buf, &i, "not out of time") ||
3404                 looking_at(buf, &i, "either player is out of time") ||
3405                 looking_at(buf, &i, "has timeseal; checking")) {
3406                 /* We must have called his flag a little too soon */
3407                 whiteFlag = blackFlag = FALSE;
3408                 continue;
3409             }
3410
3411             if (looking_at(buf, &i, "added * seconds to") ||
3412                 looking_at(buf, &i, "seconds were added to")) {
3413                 /* Update the clocks */
3414                 SendToICS(ics_prefix);
3415                 SendToICS("refresh\n");
3416                 continue;
3417             }
3418
3419             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3420                 ics_clock_paused = TRUE;
3421                 StopClocks();
3422                 continue;
3423             }
3424
3425             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3426                 ics_clock_paused = FALSE;
3427                 StartClocks();
3428                 continue;
3429             }
3430
3431             /* Grab player ratings from the Creating: message.
3432                Note we have to check for the special case when
3433                the ICS inserts things like [white] or [black]. */
3434             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3435                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3436                 /* star_matches:
3437                    0    player 1 name (not necessarily white)
3438                    1    player 1 rating
3439                    2    empty, white, or black (IGNORED)
3440                    3    player 2 name (not necessarily black)
3441                    4    player 2 rating
3442
3443                    The names/ratings are sorted out when the game
3444                    actually starts (below).
3445                 */
3446                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3447                 player1Rating = string_to_rating(star_match[1]);
3448                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3449                 player2Rating = string_to_rating(star_match[4]);
3450
3451                 if (appData.debugMode)
3452                   fprintf(debugFP,
3453                           "Ratings from 'Creating:' %s %d, %s %d\n",
3454                           player1Name, player1Rating,
3455                           player2Name, player2Rating);
3456
3457                 continue;
3458             }
3459
3460             /* Improved generic start/end-of-game messages */
3461             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3462                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3463                 /* If tkind == 0: */
3464                 /* star_match[0] is the game number */
3465                 /*           [1] is the white player's name */
3466                 /*           [2] is the black player's name */
3467                 /* For end-of-game: */
3468                 /*           [3] is the reason for the game end */
3469                 /*           [4] is a PGN end game-token, preceded by " " */
3470                 /* For start-of-game: */
3471                 /*           [3] begins with "Creating" or "Continuing" */
3472                 /*           [4] is " *" or empty (don't care). */
3473                 int gamenum = atoi(star_match[0]);
3474                 char *whitename, *blackname, *why, *endtoken;
3475                 ChessMove endtype = (ChessMove) 0;
3476
3477                 if (tkind == 0) {
3478                   whitename = star_match[1];
3479                   blackname = star_match[2];
3480                   why = star_match[3];
3481                   endtoken = star_match[4];
3482                 } else {
3483                   whitename = star_match[1];
3484                   blackname = star_match[3];
3485                   why = star_match[5];
3486                   endtoken = star_match[6];
3487                 }
3488
3489                 /* Game start messages */
3490                 if (strncmp(why, "Creating ", 9) == 0 ||
3491                     strncmp(why, "Continuing ", 11) == 0) {
3492                     gs_gamenum = gamenum;
3493                     strcpy(gs_kind, strchr(why, ' ') + 1);
3494                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3495 #if ZIPPY
3496                     if (appData.zippyPlay) {
3497                         ZippyGameStart(whitename, blackname);
3498                     }
3499 #endif /*ZIPPY*/
3500                     continue;
3501                 }
3502
3503                 /* Game end messages */
3504                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3505                     ics_gamenum != gamenum) {
3506                     continue;
3507                 }
3508                 while (endtoken[0] == ' ') endtoken++;
3509                 switch (endtoken[0]) {
3510                   case '*':
3511                   default:
3512                     endtype = GameUnfinished;
3513                     break;
3514                   case '0':
3515                     endtype = BlackWins;
3516                     break;
3517                   case '1':
3518                     if (endtoken[1] == '/')
3519                       endtype = GameIsDrawn;
3520                     else
3521                       endtype = WhiteWins;
3522                     break;
3523                 }
3524                 GameEnds(endtype, why, GE_ICS);
3525 #if ZIPPY
3526                 if (appData.zippyPlay && first.initDone) {
3527                     ZippyGameEnd(endtype, why);
3528                     if (first.pr == NULL) {
3529                       /* Start the next process early so that we'll
3530                          be ready for the next challenge */
3531                       StartChessProgram(&first);
3532                     }
3533                     /* Send "new" early, in case this command takes
3534                        a long time to finish, so that we'll be ready
3535                        for the next challenge. */
3536                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3537                     Reset(TRUE, TRUE);
3538                 }
3539 #endif /*ZIPPY*/
3540                 continue;
3541             }
3542
3543             if (looking_at(buf, &i, "Removing game * from observation") ||
3544                 looking_at(buf, &i, "no longer observing game *") ||
3545                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3546                 if (gameMode == IcsObserving &&
3547                     atoi(star_match[0]) == ics_gamenum)
3548                   {
3549                       /* icsEngineAnalyze */
3550                       if (appData.icsEngineAnalyze) {
3551                             ExitAnalyzeMode();
3552                             ModeHighlight();
3553                       }
3554                       StopClocks();
3555                       gameMode = IcsIdle;
3556                       ics_gamenum = -1;
3557                       ics_user_moved = FALSE;
3558                   }
3559                 continue;
3560             }
3561
3562             if (looking_at(buf, &i, "no longer examining game *")) {
3563                 if (gameMode == IcsExamining &&
3564                     atoi(star_match[0]) == ics_gamenum)
3565                   {
3566                       gameMode = IcsIdle;
3567                       ics_gamenum = -1;
3568                       ics_user_moved = FALSE;
3569                   }
3570                 continue;
3571             }
3572
3573             /* Advance leftover_start past any newlines we find,
3574                so only partial lines can get reparsed */
3575             if (looking_at(buf, &i, "\n")) {
3576                 prevColor = curColor;
3577                 if (curColor != ColorNormal) {
3578                     if (oldi > next_out) {
3579                         SendToPlayer(&buf[next_out], oldi - next_out);
3580                         next_out = oldi;
3581                     }
3582                     Colorize(ColorNormal, FALSE);
3583                     curColor = ColorNormal;
3584                 }
3585                 if (started == STARTED_BOARD) {
3586                     started = STARTED_NONE;
3587                     parse[parse_pos] = NULLCHAR;
3588                     ParseBoard12(parse);
3589                     ics_user_moved = 0;
3590
3591                     /* Send premove here */
3592                     if (appData.premove) {
3593                       char str[MSG_SIZ];
3594                       if (currentMove == 0 &&
3595                           gameMode == IcsPlayingWhite &&
3596                           appData.premoveWhite) {
3597                         sprintf(str, "%s\n", appData.premoveWhiteText);
3598                         if (appData.debugMode)
3599                           fprintf(debugFP, "Sending premove:\n");
3600                         SendToICS(str);
3601                       } else if (currentMove == 1 &&
3602                                  gameMode == IcsPlayingBlack &&
3603                                  appData.premoveBlack) {
3604                         sprintf(str, "%s\n", appData.premoveBlackText);
3605                         if (appData.debugMode)
3606                           fprintf(debugFP, "Sending premove:\n");
3607                         SendToICS(str);
3608                       } else if (gotPremove) {
3609                         gotPremove = 0;
3610                         ClearPremoveHighlights();
3611                         if (appData.debugMode)
3612                           fprintf(debugFP, "Sending premove:\n");
3613                           UserMoveEvent(premoveFromX, premoveFromY,
3614                                         premoveToX, premoveToY,
3615                                         premovePromoChar);
3616                       }
3617                     }
3618
3619                     /* Usually suppress following prompt */
3620                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3621                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3622                         if (looking_at(buf, &i, "*% ")) {
3623                             savingComment = FALSE;
3624                             suppressKibitz = 0;
3625                         }
3626                     }
3627                     next_out = i;
3628                 } else if (started == STARTED_HOLDINGS) {
3629                     int gamenum;
3630                     char new_piece[MSG_SIZ];
3631                     started = STARTED_NONE;
3632                     parse[parse_pos] = NULLCHAR;
3633                     if (appData.debugMode)
3634                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3635                                                         parse, currentMove);
3636                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3637                         gamenum == ics_gamenum) {
3638                         if (gameInfo.variant == VariantNormal) {
3639                           /* [HGM] We seem to switch variant during a game!
3640                            * Presumably no holdings were displayed, so we have
3641                            * to move the position two files to the right to
3642                            * create room for them!
3643                            */
3644                           VariantClass newVariant;
3645                           switch(gameInfo.boardWidth) { // base guess on board width
3646                                 case 9:  newVariant = VariantShogi; break;
3647                                 case 10: newVariant = VariantGreat; break;
3648                                 default: newVariant = VariantCrazyhouse; break;
3649                           }
3650                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3651                           /* Get a move list just to see the header, which
3652                              will tell us whether this is really bug or zh */
3653                           if (ics_getting_history == H_FALSE) {
3654                             ics_getting_history = H_REQUESTED;
3655                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3656                             SendToICS(str);
3657                           }
3658                         }
3659                         new_piece[0] = NULLCHAR;
3660                         sscanf(parse, "game %d white [%s black [%s <- %s",
3661                                &gamenum, white_holding, black_holding,
3662                                new_piece);
3663                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3664                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3665                         /* [HGM] copy holdings to board holdings area */
3666                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3667                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3668                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3669 #if ZIPPY
3670                         if (appData.zippyPlay && first.initDone) {
3671                             ZippyHoldings(white_holding, black_holding,
3672                                           new_piece);
3673                         }
3674 #endif /*ZIPPY*/
3675                         if (tinyLayout || smallLayout) {
3676                             char wh[16], bh[16];
3677                             PackHolding(wh, white_holding);
3678                             PackHolding(bh, black_holding);
3679                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3680                                     gameInfo.white, gameInfo.black);
3681                         } else {
3682                             sprintf(str, "%s [%s] vs. %s [%s]",
3683                                     gameInfo.white, white_holding,
3684                                     gameInfo.black, black_holding);
3685                         }
3686
3687                         DrawPosition(FALSE, boards[currentMove]);
3688                         DisplayTitle(str);
3689                     }
3690                     /* Suppress following prompt */
3691                     if (looking_at(buf, &i, "*% ")) {
3692                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3693                         savingComment = FALSE;
3694                         suppressKibitz = 0;
3695                     }
3696                     next_out = i;
3697                 }
3698                 continue;
3699             }
3700
3701             i++;                /* skip unparsed character and loop back */
3702         }
3703         
3704         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3705 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3706 //          SendToPlayer(&buf[next_out], i - next_out);
3707             started != STARTED_HOLDINGS && leftover_start > next_out) {
3708             SendToPlayer(&buf[next_out], leftover_start - next_out);
3709             next_out = i;
3710         }
3711
3712         leftover_len = buf_len - leftover_start;
3713         /* if buffer ends with something we couldn't parse,
3714            reparse it after appending the next read */
3715
3716     } else if (count == 0) {
3717         RemoveInputSource(isr);
3718         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3719     } else {
3720         DisplayFatalError(_("Error reading from ICS"), error, 1);
3721     }
3722 }
3723
3724
3725 /* Board style 12 looks like this:
3726
3727    <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
3728
3729  * The "<12> " is stripped before it gets to this routine.  The two
3730  * trailing 0's (flip state and clock ticking) are later addition, and
3731  * some chess servers may not have them, or may have only the first.
3732  * Additional trailing fields may be added in the future.
3733  */
3734
3735 #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"
3736
3737 #define RELATION_OBSERVING_PLAYED    0
3738 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3739 #define RELATION_PLAYING_MYMOVE      1
3740 #define RELATION_PLAYING_NOTMYMOVE  -1
3741 #define RELATION_EXAMINING           2
3742 #define RELATION_ISOLATED_BOARD     -3
3743 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3744
3745 void
3746 ParseBoard12(string)
3747      char *string;
3748 {
3749     GameMode newGameMode;
3750     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3751     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3752     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3753     char to_play, board_chars[200];
3754     char move_str[500], str[500], elapsed_time[500];
3755     char black[32], white[32];
3756     Board board;
3757     int prevMove = currentMove;
3758     int ticking = 2;
3759     ChessMove moveType;
3760     int fromX, fromY, toX, toY;
3761     char promoChar;
3762     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3763     char *bookHit = NULL; // [HGM] book
3764     Boolean weird = FALSE, reqFlag = FALSE;
3765
3766     fromX = fromY = toX = toY = -1;
3767
3768     newGame = FALSE;
3769
3770     if (appData.debugMode)
3771       fprintf(debugFP, _("Parsing board: %s\n"), string);
3772
3773     move_str[0] = NULLCHAR;
3774     elapsed_time[0] = NULLCHAR;
3775     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3776         int  i = 0, j;
3777         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3778             if(string[i] == ' ') { ranks++; files = 0; }
3779             else files++;
3780             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3781             i++;
3782         }
3783         for(j = 0; j <i; j++) board_chars[j] = string[j];
3784         board_chars[i] = '\0';
3785         string += i + 1;
3786     }
3787     n = sscanf(string, PATTERN, &to_play, &double_push,
3788                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3789                &gamenum, white, black, &relation, &basetime, &increment,
3790                &white_stren, &black_stren, &white_time, &black_time,
3791                &moveNum, str, elapsed_time, move_str, &ics_flip,
3792                &ticking);
3793
3794     if (n < 21) {
3795         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3796         DisplayError(str, 0);
3797         return;
3798     }
3799
3800     /* Convert the move number to internal form */
3801     moveNum = (moveNum - 1) * 2;
3802     if (to_play == 'B') moveNum++;
3803     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3804       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3805                         0, 1);
3806       return;
3807     }
3808
3809     switch (relation) {
3810       case RELATION_OBSERVING_PLAYED:
3811       case RELATION_OBSERVING_STATIC:
3812         if (gamenum == -1) {
3813             /* Old ICC buglet */
3814             relation = RELATION_OBSERVING_STATIC;
3815         }
3816         newGameMode = IcsObserving;
3817         break;
3818       case RELATION_PLAYING_MYMOVE:
3819       case RELATION_PLAYING_NOTMYMOVE:
3820         newGameMode =
3821           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3822             IcsPlayingWhite : IcsPlayingBlack;
3823         break;
3824       case RELATION_EXAMINING:
3825         newGameMode = IcsExamining;
3826         break;
3827       case RELATION_ISOLATED_BOARD:
3828       default:
3829         /* Just display this board.  If user was doing something else,
3830            we will forget about it until the next board comes. */
3831         newGameMode = IcsIdle;
3832         break;
3833       case RELATION_STARTING_POSITION:
3834         newGameMode = gameMode;
3835         break;
3836     }
3837
3838     /* Modify behavior for initial board display on move listing
3839        of wild games.
3840        */
3841     switch (ics_getting_history) {
3842       case H_FALSE:
3843       case H_REQUESTED:
3844         break;
3845       case H_GOT_REQ_HEADER:
3846       case H_GOT_UNREQ_HEADER:
3847         /* This is the initial position of the current game */
3848         gamenum = ics_gamenum;
3849         moveNum = 0;            /* old ICS bug workaround */
3850         if (to_play == 'B') {
3851           startedFromSetupPosition = TRUE;
3852           blackPlaysFirst = TRUE;
3853           moveNum = 1;
3854           if (forwardMostMove == 0) forwardMostMove = 1;
3855           if (backwardMostMove == 0) backwardMostMove = 1;
3856           if (currentMove == 0) currentMove = 1;
3857         }
3858         newGameMode = gameMode;
3859         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3860         break;
3861       case H_GOT_UNWANTED_HEADER:
3862         /* This is an initial board that we don't want */
3863         return;
3864       case H_GETTING_MOVES:
3865         /* Should not happen */
3866         DisplayError(_("Error gathering move list: extra board"), 0);
3867         ics_getting_history = H_FALSE;
3868         return;
3869     }
3870
3871    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3872                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3873      /* [HGM] We seem to have switched variant unexpectedly
3874       * Try to guess new variant from board size
3875       */
3876           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3877           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3878           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3879           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3880           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3881           if(!weird) newVariant = VariantNormal;
3882           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3883           /* Get a move list just to see the header, which
3884              will tell us whether this is really bug or zh */
3885           if (ics_getting_history == H_FALSE) {
3886             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3887             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3888             SendToICS(str);
3889           }
3890     }
3891     
3892     /* Take action if this is the first board of a new game, or of a
3893        different game than is currently being displayed.  */
3894     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3895         relation == RELATION_ISOLATED_BOARD) {
3896
3897         /* Forget the old game and get the history (if any) of the new one */
3898         if (gameMode != BeginningOfGame) {
3899           Reset(TRUE, TRUE);
3900         }
3901         newGame = TRUE;
3902         if (appData.autoRaiseBoard) BoardToTop();
3903         prevMove = -3;
3904         if (gamenum == -1) {
3905             newGameMode = IcsIdle;
3906         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3907                    appData.getMoveList && !reqFlag) {
3908             /* Need to get game history */
3909             ics_getting_history = H_REQUESTED;
3910             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3911             SendToICS(str);
3912         }
3913
3914         /* Initially flip the board to have black on the bottom if playing
3915            black or if the ICS flip flag is set, but let the user change
3916            it with the Flip View button. */
3917         flipView = appData.autoFlipView ?
3918           (newGameMode == IcsPlayingBlack) || ics_flip :
3919           appData.flipView;
3920
3921         /* Done with values from previous mode; copy in new ones */
3922         gameMode = newGameMode;
3923         ModeHighlight();
3924         ics_gamenum = gamenum;
3925         if (gamenum == gs_gamenum) {
3926             int klen = strlen(gs_kind);
3927             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3928             sprintf(str, "ICS %s", gs_kind);
3929             gameInfo.event = StrSave(str);
3930         } else {
3931             gameInfo.event = StrSave("ICS game");
3932         }
3933         gameInfo.site = StrSave(appData.icsHost);
3934         gameInfo.date = PGNDate();
3935         gameInfo.round = StrSave("-");
3936         gameInfo.white = StrSave(white);
3937         gameInfo.black = StrSave(black);
3938         timeControl = basetime * 60 * 1000;
3939         timeControl_2 = 0;
3940         timeIncrement = increment * 1000;
3941         movesPerSession = 0;
3942         gameInfo.timeControl = TimeControlTagValue();
3943         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3944   if (appData.debugMode) {
3945     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3946     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3947     setbuf(debugFP, NULL);
3948   }
3949
3950         gameInfo.outOfBook = NULL;
3951
3952         /* Do we have the ratings? */
3953         if (strcmp(player1Name, white) == 0 &&
3954             strcmp(player2Name, black) == 0) {
3955             if (appData.debugMode)
3956               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3957                       player1Rating, player2Rating);
3958             gameInfo.whiteRating = player1Rating;
3959             gameInfo.blackRating = player2Rating;
3960         } else if (strcmp(player2Name, white) == 0 &&
3961                    strcmp(player1Name, black) == 0) {
3962             if (appData.debugMode)
3963               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3964                       player2Rating, player1Rating);
3965             gameInfo.whiteRating = player2Rating;
3966             gameInfo.blackRating = player1Rating;
3967         }
3968         player1Name[0] = player2Name[0] = NULLCHAR;
3969
3970         /* Silence shouts if requested */
3971         if (appData.quietPlay &&
3972             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3973             SendToICS(ics_prefix);
3974             SendToICS("set shout 0\n");
3975         }
3976     }
3977
3978     /* Deal with midgame name changes */
3979     if (!newGame) {
3980         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3981             if (gameInfo.white) free(gameInfo.white);
3982             gameInfo.white = StrSave(white);
3983         }
3984         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3985             if (gameInfo.black) free(gameInfo.black);
3986             gameInfo.black = StrSave(black);
3987         }
3988     }
3989
3990     /* Throw away game result if anything actually changes in examine mode */
3991     if (gameMode == IcsExamining && !newGame) {
3992         gameInfo.result = GameUnfinished;
3993         if (gameInfo.resultDetails != NULL) {
3994             free(gameInfo.resultDetails);
3995             gameInfo.resultDetails = NULL;
3996         }
3997     }
3998
3999     /* In pausing && IcsExamining mode, we ignore boards coming
4000        in if they are in a different variation than we are. */
4001     if (pauseExamInvalid) return;
4002     if (pausing && gameMode == IcsExamining) {
4003         if (moveNum <= pauseExamForwardMostMove) {
4004             pauseExamInvalid = TRUE;
4005             forwardMostMove = pauseExamForwardMostMove;
4006             return;
4007         }
4008     }
4009
4010   if (appData.debugMode) {
4011     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4012   }
4013     /* Parse the board */
4014     for (k = 0; k < ranks; k++) {
4015       for (j = 0; j < files; j++)
4016         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4017       if(gameInfo.holdingsWidth > 1) {
4018            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4019            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4020       }
4021     }
4022     CopyBoard(boards[moveNum], board);
4023     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4024     if (moveNum == 0) {
4025         startedFromSetupPosition =
4026           !CompareBoards(board, initialPosition);
4027         if(startedFromSetupPosition)
4028             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4029     }
4030
4031     /* [HGM] Set castling rights. Take the outermost Rooks,
4032        to make it also work for FRC opening positions. Note that board12
4033        is really defective for later FRC positions, as it has no way to
4034        indicate which Rook can castle if they are on the same side of King.
4035        For the initial position we grant rights to the outermost Rooks,
4036        and remember thos rights, and we then copy them on positions
4037        later in an FRC game. This means WB might not recognize castlings with
4038        Rooks that have moved back to their original position as illegal,
4039        but in ICS mode that is not its job anyway.
4040     */
4041     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4042     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4043
4044         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4045             if(board[0][i] == WhiteRook) j = i;
4046         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4047         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4048             if(board[0][i] == WhiteRook) j = i;
4049         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4050         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4051             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4052         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4053         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4054             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4055         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4056
4057         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4058         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4059             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4060         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4061             if(board[BOARD_HEIGHT-1][k] == bKing)
4062                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4063         if(gameInfo.variant == VariantTwoKings) {
4064             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4065             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4066             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4067         }
4068     } else { int r;
4069         r = boards[moveNum][CASTLING][0] = initialRights[0];
4070         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4071         r = boards[moveNum][CASTLING][1] = initialRights[1];
4072         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4073         r = boards[moveNum][CASTLING][3] = initialRights[3];
4074         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4075         r = boards[moveNum][CASTLING][4] = initialRights[4];
4076         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4077         /* wildcastle kludge: always assume King has rights */
4078         r = boards[moveNum][CASTLING][2] = initialRights[2];
4079         r = boards[moveNum][CASTLING][5] = initialRights[5];
4080     }
4081     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4082     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4083
4084
4085     if (ics_getting_history == H_GOT_REQ_HEADER ||
4086         ics_getting_history == H_GOT_UNREQ_HEADER) {
4087         /* This was an initial position from a move list, not
4088            the current position */
4089         return;
4090     }
4091
4092     /* Update currentMove and known move number limits */
4093     newMove = newGame || moveNum > forwardMostMove;
4094
4095     if (newGame) {
4096         forwardMostMove = backwardMostMove = currentMove = moveNum;
4097         if (gameMode == IcsExamining && moveNum == 0) {
4098           /* Workaround for ICS limitation: we are not told the wild
4099              type when starting to examine a game.  But if we ask for
4100              the move list, the move list header will tell us */
4101             ics_getting_history = H_REQUESTED;
4102             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4103             SendToICS(str);
4104         }
4105     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4106                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4107 #if ZIPPY
4108         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4109         /* [HGM] applied this also to an engine that is silently watching        */
4110         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4111             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4112             gameInfo.variant == currentlyInitializedVariant) {
4113           takeback = forwardMostMove - moveNum;
4114           for (i = 0; i < takeback; i++) {
4115             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4116             SendToProgram("undo\n", &first);
4117           }
4118         }
4119 #endif
4120
4121         forwardMostMove = moveNum;
4122         if (!pausing || currentMove > forwardMostMove)
4123           currentMove = forwardMostMove;
4124     } else {
4125         /* New part of history that is not contiguous with old part */
4126         if (pausing && gameMode == IcsExamining) {
4127             pauseExamInvalid = TRUE;
4128             forwardMostMove = pauseExamForwardMostMove;
4129             return;
4130         }
4131         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4132 #if ZIPPY
4133             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4134                 // [HGM] when we will receive the move list we now request, it will be
4135                 // fed to the engine from the first move on. So if the engine is not
4136                 // in the initial position now, bring it there.
4137                 InitChessProgram(&first, 0);
4138             }
4139 #endif
4140             ics_getting_history = H_REQUESTED;
4141             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4142             SendToICS(str);
4143         }
4144         forwardMostMove = backwardMostMove = currentMove = moveNum;
4145     }
4146
4147     /* Update the clocks */
4148     if (strchr(elapsed_time, '.')) {
4149       /* Time is in ms */
4150       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4151       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4152     } else {
4153       /* Time is in seconds */
4154       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4155       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4156     }
4157
4158
4159 #if ZIPPY
4160     if (appData.zippyPlay && newGame &&
4161         gameMode != IcsObserving && gameMode != IcsIdle &&
4162         gameMode != IcsExamining)
4163       ZippyFirstBoard(moveNum, basetime, increment);
4164 #endif
4165
4166     /* Put the move on the move list, first converting
4167        to canonical algebraic form. */
4168     if (moveNum > 0) {
4169   if (appData.debugMode) {
4170     if (appData.debugMode) { int f = forwardMostMove;
4171         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4172                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4173                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4174     }
4175     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4176     fprintf(debugFP, "moveNum = %d\n", moveNum);
4177     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4178     setbuf(debugFP, NULL);
4179   }
4180         if (moveNum <= backwardMostMove) {
4181             /* We don't know what the board looked like before
4182                this move.  Punt. */
4183             strcpy(parseList[moveNum - 1], move_str);
4184             strcat(parseList[moveNum - 1], " ");
4185             strcat(parseList[moveNum - 1], elapsed_time);
4186             moveList[moveNum - 1][0] = NULLCHAR;
4187         } else if (strcmp(move_str, "none") == 0) {
4188             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4189             /* Again, we don't know what the board looked like;
4190                this is really the start of the game. */
4191             parseList[moveNum - 1][0] = NULLCHAR;
4192             moveList[moveNum - 1][0] = NULLCHAR;
4193             backwardMostMove = moveNum;
4194             startedFromSetupPosition = TRUE;
4195             fromX = fromY = toX = toY = -1;
4196         } else {
4197           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4198           //                 So we parse the long-algebraic move string in stead of the SAN move
4199           int valid; char buf[MSG_SIZ], *prom;
4200
4201           // str looks something like "Q/a1-a2"; kill the slash
4202           if(str[1] == '/')
4203                 sprintf(buf, "%c%s", str[0], str+2);
4204           else  strcpy(buf, str); // might be castling
4205           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4206                 strcat(buf, prom); // long move lacks promo specification!
4207           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4208                 if(appData.debugMode)
4209                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4210                 strcpy(move_str, buf);
4211           }
4212           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4213                                 &fromX, &fromY, &toX, &toY, &promoChar)
4214                || ParseOneMove(buf, moveNum - 1, &moveType,
4215                                 &fromX, &fromY, &toX, &toY, &promoChar);
4216           // end of long SAN patch
4217           if (valid) {
4218             (void) CoordsToAlgebraic(boards[moveNum - 1],
4219                                      PosFlags(moveNum - 1),
4220                                      fromY, fromX, toY, toX, promoChar,
4221                                      parseList[moveNum-1]);
4222             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4223               case MT_NONE:
4224               case MT_STALEMATE:
4225               default:
4226                 break;
4227               case MT_CHECK:
4228                 if(gameInfo.variant != VariantShogi)
4229                     strcat(parseList[moveNum - 1], "+");
4230                 break;
4231               case MT_CHECKMATE:
4232               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4233                 strcat(parseList[moveNum - 1], "#");
4234                 break;
4235             }
4236             strcat(parseList[moveNum - 1], " ");
4237             strcat(parseList[moveNum - 1], elapsed_time);
4238             /* currentMoveString is set as a side-effect of ParseOneMove */
4239             strcpy(moveList[moveNum - 1], currentMoveString);
4240             strcat(moveList[moveNum - 1], "\n");
4241           } else {
4242             /* Move from ICS was illegal!?  Punt. */
4243   if (appData.debugMode) {
4244     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4245     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4246   }
4247             strcpy(parseList[moveNum - 1], move_str);
4248             strcat(parseList[moveNum - 1], " ");
4249             strcat(parseList[moveNum - 1], elapsed_time);
4250             moveList[moveNum - 1][0] = NULLCHAR;
4251             fromX = fromY = toX = toY = -1;
4252           }
4253         }
4254   if (appData.debugMode) {
4255     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4256     setbuf(debugFP, NULL);
4257   }
4258
4259 #if ZIPPY
4260         /* Send move to chess program (BEFORE animating it). */
4261         if (appData.zippyPlay && !newGame && newMove &&
4262            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4263
4264             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4265                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4266                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4267                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4268                             move_str);
4269                     DisplayError(str, 0);
4270                 } else {
4271                     if (first.sendTime) {
4272                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4273                     }
4274                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4275                     if (firstMove && !bookHit) {
4276                         firstMove = FALSE;
4277                         if (first.useColors) {
4278                           SendToProgram(gameMode == IcsPlayingWhite ?
4279                                         "white\ngo\n" :
4280                                         "black\ngo\n", &first);
4281                         } else {
4282                           SendToProgram("go\n", &first);
4283                         }
4284                         first.maybeThinking = TRUE;
4285                     }
4286                 }
4287             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4288               if (moveList[moveNum - 1][0] == NULLCHAR) {
4289                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4290                 DisplayError(str, 0);
4291               } else {
4292                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4293                 SendMoveToProgram(moveNum - 1, &first);
4294               }
4295             }
4296         }
4297 #endif
4298     }
4299
4300     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4301         /* If move comes from a remote source, animate it.  If it
4302            isn't remote, it will have already been animated. */
4303         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4304             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4305         }
4306         if (!pausing && appData.highlightLastMove) {
4307             SetHighlights(fromX, fromY, toX, toY);
4308         }
4309     }
4310
4311     /* Start the clocks */
4312     whiteFlag = blackFlag = FALSE;
4313     appData.clockMode = !(basetime == 0 && increment == 0);
4314     if (ticking == 0) {
4315       ics_clock_paused = TRUE;
4316       StopClocks();
4317     } else if (ticking == 1) {
4318       ics_clock_paused = FALSE;
4319     }
4320     if (gameMode == IcsIdle ||
4321         relation == RELATION_OBSERVING_STATIC ||
4322         relation == RELATION_EXAMINING ||
4323         ics_clock_paused)
4324       DisplayBothClocks();
4325     else
4326       StartClocks();
4327
4328     /* Display opponents and material strengths */
4329     if (gameInfo.variant != VariantBughouse &&
4330         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4331         if (tinyLayout || smallLayout) {
4332             if(gameInfo.variant == VariantNormal)
4333                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4334                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4335                     basetime, increment);
4336             else
4337                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4338                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4339                     basetime, increment, (int) gameInfo.variant);
4340         } else {
4341             if(gameInfo.variant == VariantNormal)
4342                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4343                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4344                     basetime, increment);
4345             else
4346                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4347                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4348                     basetime, increment, VariantName(gameInfo.variant));
4349         }
4350         DisplayTitle(str);
4351   if (appData.debugMode) {
4352     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4353   }
4354     }
4355
4356
4357     /* Display the board */
4358     if (!pausing && !appData.noGUI) {
4359       if (appData.premove)
4360           if (!gotPremove ||
4361              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4362              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4363               ClearPremoveHighlights();
4364
4365       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4366       DrawPosition(j, boards[currentMove]);
4367
4368       DisplayMove(moveNum - 1);
4369       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4370             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4371               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4372         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4373       }
4374     }
4375
4376     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4377 #if ZIPPY
4378     if(bookHit) { // [HGM] book: simulate book reply
4379         static char bookMove[MSG_SIZ]; // a bit generous?
4380
4381         programStats.nodes = programStats.depth = programStats.time =
4382         programStats.score = programStats.got_only_move = 0;
4383         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4384
4385         strcpy(bookMove, "move ");
4386         strcat(bookMove, bookHit);
4387         HandleMachineMove(bookMove, &first);
4388     }
4389 #endif
4390 }
4391
4392 void
4393 GetMoveListEvent()
4394 {
4395     char buf[MSG_SIZ];
4396     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4397         ics_getting_history = H_REQUESTED;
4398         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4399         SendToICS(buf);
4400     }
4401 }
4402
4403 void
4404 AnalysisPeriodicEvent(force)
4405      int force;
4406 {
4407     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4408          && !force) || !appData.periodicUpdates)
4409       return;
4410
4411     /* Send . command to Crafty to collect stats */
4412     SendToProgram(".\n", &first);
4413
4414     /* Don't send another until we get a response (this makes
4415        us stop sending to old Crafty's which don't understand
4416        the "." command (sending illegal cmds resets node count & time,
4417        which looks bad)) */
4418     programStats.ok_to_send = 0;
4419 }
4420
4421 void ics_update_width(new_width)
4422         int new_width;
4423 {
4424         ics_printf("set width %d\n", new_width);
4425 }
4426
4427 void
4428 SendMoveToProgram(moveNum, cps)
4429      int moveNum;
4430      ChessProgramState *cps;
4431 {
4432     char buf[MSG_SIZ];
4433
4434     if (cps->useUsermove) {
4435       SendToProgram("usermove ", cps);
4436     }
4437     if (cps->useSAN) {
4438       char *space;
4439       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4440         int len = space - parseList[moveNum];
4441         memcpy(buf, parseList[moveNum], len);
4442         buf[len++] = '\n';
4443         buf[len] = NULLCHAR;
4444       } else {
4445         sprintf(buf, "%s\n", parseList[moveNum]);
4446       }
4447       SendToProgram(buf, cps);
4448     } else {
4449       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4450         AlphaRank(moveList[moveNum], 4);
4451         SendToProgram(moveList[moveNum], cps);
4452         AlphaRank(moveList[moveNum], 4); // and back
4453       } else
4454       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4455        * the engine. It would be nice to have a better way to identify castle
4456        * moves here. */
4457       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4458                                                                          && cps->useOOCastle) {
4459         int fromX = moveList[moveNum][0] - AAA;
4460         int fromY = moveList[moveNum][1] - ONE;
4461         int toX = moveList[moveNum][2] - AAA;
4462         int toY = moveList[moveNum][3] - ONE;
4463         if((boards[moveNum][fromY][fromX] == WhiteKing
4464             && boards[moveNum][toY][toX] == WhiteRook)
4465            || (boards[moveNum][fromY][fromX] == BlackKing
4466                && boards[moveNum][toY][toX] == BlackRook)) {
4467           if(toX > fromX) SendToProgram("O-O\n", cps);
4468           else SendToProgram("O-O-O\n", cps);
4469         }
4470         else SendToProgram(moveList[moveNum], cps);
4471       }
4472       else SendToProgram(moveList[moveNum], cps);
4473       /* End of additions by Tord */
4474     }
4475
4476     /* [HGM] setting up the opening has brought engine in force mode! */
4477     /*       Send 'go' if we are in a mode where machine should play. */
4478     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4479         (gameMode == TwoMachinesPlay   ||
4480 #ifdef ZIPPY
4481          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4482 #endif
4483          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4484         SendToProgram("go\n", cps);
4485   if (appData.debugMode) {
4486     fprintf(debugFP, "(extra)\n");
4487   }
4488     }
4489     setboardSpoiledMachineBlack = 0;
4490 }
4491
4492 void
4493 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4494      ChessMove moveType;
4495      int fromX, fromY, toX, toY;
4496 {
4497     char user_move[MSG_SIZ];
4498
4499     switch (moveType) {
4500       default:
4501         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4502                 (int)moveType, fromX, fromY, toX, toY);
4503         DisplayError(user_move + strlen("say "), 0);
4504         break;
4505       case WhiteKingSideCastle:
4506       case BlackKingSideCastle:
4507       case WhiteQueenSideCastleWild:
4508       case BlackQueenSideCastleWild:
4509       /* PUSH Fabien */
4510       case WhiteHSideCastleFR:
4511       case BlackHSideCastleFR:
4512       /* POP Fabien */
4513         sprintf(user_move, "o-o\n");
4514         break;
4515       case WhiteQueenSideCastle:
4516       case BlackQueenSideCastle:
4517       case WhiteKingSideCastleWild:
4518       case BlackKingSideCastleWild:
4519       /* PUSH Fabien */
4520       case WhiteASideCastleFR:
4521       case BlackASideCastleFR:
4522       /* POP Fabien */
4523         sprintf(user_move, "o-o-o\n");
4524         break;
4525       case WhitePromotionQueen:
4526       case BlackPromotionQueen:
4527       case WhitePromotionRook:
4528       case BlackPromotionRook:
4529       case WhitePromotionBishop:
4530       case BlackPromotionBishop:
4531       case WhitePromotionKnight:
4532       case BlackPromotionKnight:
4533       case WhitePromotionKing:
4534       case BlackPromotionKing:
4535       case WhitePromotionChancellor:
4536       case BlackPromotionChancellor:
4537       case WhitePromotionArchbishop:
4538       case BlackPromotionArchbishop:
4539         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4540             sprintf(user_move, "%c%c%c%c=%c\n",
4541                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4542                 PieceToChar(WhiteFerz));
4543         else if(gameInfo.variant == VariantGreat)
4544             sprintf(user_move, "%c%c%c%c=%c\n",
4545                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4546                 PieceToChar(WhiteMan));
4547         else
4548             sprintf(user_move, "%c%c%c%c=%c\n",
4549                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4550                 PieceToChar(PromoPiece(moveType)));
4551         break;
4552       case WhiteDrop:
4553       case BlackDrop:
4554         sprintf(user_move, "%c@%c%c\n",
4555                 ToUpper(PieceToChar((ChessSquare) fromX)),
4556                 AAA + toX, ONE + toY);
4557         break;
4558       case NormalMove:
4559       case WhiteCapturesEnPassant:
4560       case BlackCapturesEnPassant:
4561       case IllegalMove:  /* could be a variant we don't quite understand */
4562         sprintf(user_move, "%c%c%c%c\n",
4563                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4564         break;
4565     }
4566     SendToICS(user_move);
4567     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4568         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4569 }
4570
4571 void
4572 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4573      int rf, ff, rt, ft;
4574      char promoChar;
4575      char move[7];
4576 {
4577     if (rf == DROP_RANK) {
4578         sprintf(move, "%c@%c%c\n",
4579                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4580     } else {
4581         if (promoChar == 'x' || promoChar == NULLCHAR) {
4582             sprintf(move, "%c%c%c%c\n",
4583                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4584         } else {
4585             sprintf(move, "%c%c%c%c%c\n",
4586                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4587         }
4588     }
4589 }
4590
4591 void
4592 ProcessICSInitScript(f)
4593      FILE *f;
4594 {
4595     char buf[MSG_SIZ];
4596
4597     while (fgets(buf, MSG_SIZ, f)) {
4598         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4599     }
4600
4601     fclose(f);
4602 }
4603
4604
4605 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4606 void
4607 AlphaRank(char *move, int n)
4608 {
4609 //    char *p = move, c; int x, y;
4610
4611     if (appData.debugMode) {
4612         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4613     }
4614
4615     if(move[1]=='*' &&
4616        move[2]>='0' && move[2]<='9' &&
4617        move[3]>='a' && move[3]<='x'    ) {
4618         move[1] = '@';
4619         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4620         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4621     } else
4622     if(move[0]>='0' && move[0]<='9' &&
4623        move[1]>='a' && move[1]<='x' &&
4624        move[2]>='0' && move[2]<='9' &&
4625        move[3]>='a' && move[3]<='x'    ) {
4626         /* input move, Shogi -> normal */
4627         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4628         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4629         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4630         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4631     } else
4632     if(move[1]=='@' &&
4633        move[3]>='0' && move[3]<='9' &&
4634        move[2]>='a' && move[2]<='x'    ) {
4635         move[1] = '*';
4636         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4637         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4638     } else
4639     if(
4640        move[0]>='a' && move[0]<='x' &&
4641        move[3]>='0' && move[3]<='9' &&
4642        move[2]>='a' && move[2]<='x'    ) {
4643          /* output move, normal -> Shogi */
4644         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4645         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4646         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4647         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4648         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4649     }
4650     if (appData.debugMode) {
4651         fprintf(debugFP, "   out = '%s'\n", move);
4652     }
4653 }
4654
4655 /* Parser for moves from gnuchess, ICS, or user typein box */
4656 Boolean
4657 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4658      char *move;
4659      int moveNum;
4660      ChessMove *moveType;
4661      int *fromX, *fromY, *toX, *toY;
4662      char *promoChar;
4663 {
4664     if (appData.debugMode) {
4665         fprintf(debugFP, "move to parse: %s\n", move);
4666     }
4667     *moveType = yylexstr(moveNum, move);
4668
4669     switch (*moveType) {
4670       case WhitePromotionChancellor:
4671       case BlackPromotionChancellor:
4672       case WhitePromotionArchbishop:
4673       case BlackPromotionArchbishop:
4674       case WhitePromotionQueen:
4675       case BlackPromotionQueen:
4676       case WhitePromotionRook:
4677       case BlackPromotionRook:
4678       case WhitePromotionBishop:
4679       case BlackPromotionBishop:
4680       case WhitePromotionKnight:
4681       case BlackPromotionKnight:
4682       case WhitePromotionKing:
4683       case BlackPromotionKing:
4684       case NormalMove:
4685       case WhiteCapturesEnPassant:
4686       case BlackCapturesEnPassant:
4687       case WhiteKingSideCastle:
4688       case WhiteQueenSideCastle:
4689       case BlackKingSideCastle:
4690       case BlackQueenSideCastle:
4691       case WhiteKingSideCastleWild:
4692       case WhiteQueenSideCastleWild:
4693       case BlackKingSideCastleWild:
4694       case BlackQueenSideCastleWild:
4695       /* Code added by Tord: */
4696       case WhiteHSideCastleFR:
4697       case WhiteASideCastleFR:
4698       case BlackHSideCastleFR:
4699       case BlackASideCastleFR:
4700       /* End of code added by Tord */
4701       case IllegalMove:         /* bug or odd chess variant */
4702         *fromX = currentMoveString[0] - AAA;
4703         *fromY = currentMoveString[1] - ONE;
4704         *toX = currentMoveString[2] - AAA;
4705         *toY = currentMoveString[3] - ONE;
4706         *promoChar = currentMoveString[4];
4707         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4708             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4709     if (appData.debugMode) {
4710         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4711     }
4712             *fromX = *fromY = *toX = *toY = 0;
4713             return FALSE;
4714         }
4715         if (appData.testLegality) {
4716           return (*moveType != IllegalMove);
4717         } else {
4718           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4719                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4720         }
4721
4722       case WhiteDrop:
4723       case BlackDrop:
4724         *fromX = *moveType == WhiteDrop ?
4725           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4726           (int) CharToPiece(ToLower(currentMoveString[0]));
4727         *fromY = DROP_RANK;
4728         *toX = currentMoveString[2] - AAA;
4729         *toY = currentMoveString[3] - ONE;
4730         *promoChar = NULLCHAR;
4731         return TRUE;
4732
4733       case AmbiguousMove:
4734       case ImpossibleMove:
4735       case (ChessMove) 0:       /* end of file */
4736       case ElapsedTime:
4737       case Comment:
4738       case PGNTag:
4739       case NAG:
4740       case WhiteWins:
4741       case BlackWins:
4742       case GameIsDrawn:
4743       default:
4744     if (appData.debugMode) {
4745         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4746     }
4747         /* bug? */
4748         *fromX = *fromY = *toX = *toY = 0;
4749         *promoChar = NULLCHAR;
4750         return FALSE;
4751     }
4752 }
4753
4754
4755 void
4756 ParsePV(char *pv)
4757 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4758   int fromX, fromY, toX, toY; char promoChar;
4759   ChessMove moveType;
4760   Boolean valid;
4761   int nr = 0;
4762
4763   endPV = forwardMostMove;
4764   do {
4765     while(*pv == ' ') pv++;
4766     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4767     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4768 if(appData.debugMode){
4769 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4770 }
4771     if(!valid && nr == 0 &&
4772        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4773         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4774     }
4775     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4776     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4777     nr++;
4778     if(endPV+1 > framePtr) break; // no space, truncate
4779     if(!valid) break;
4780     endPV++;
4781     CopyBoard(boards[endPV], boards[endPV-1]);
4782     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4783     moveList[endPV-1][0] = fromX + AAA;
4784     moveList[endPV-1][1] = fromY + ONE;
4785     moveList[endPV-1][2] = toX + AAA;
4786     moveList[endPV-1][3] = toY + ONE;
4787     parseList[endPV-1][0] = NULLCHAR;
4788   } while(valid);
4789   currentMove = endPV;
4790   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4791   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4792                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4793   DrawPosition(TRUE, boards[currentMove]);
4794 }
4795
4796 static int lastX, lastY;
4797
4798 Boolean
4799 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4800 {
4801         int startPV;
4802
4803         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4804         lastX = x; lastY = y;
4805         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4806         startPV = index;
4807       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4808       index = startPV;
4809         while(buf[index] && buf[index] != '\n') index++;
4810         buf[index] = 0;
4811         ParsePV(buf+startPV);
4812         *start = startPV; *end = index-1;
4813         return TRUE;
4814 }
4815
4816 Boolean
4817 LoadPV(int x, int y)
4818 { // called on right mouse click to load PV
4819   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4820   lastX = x; lastY = y;
4821   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4822   return TRUE;
4823 }
4824
4825 void
4826 UnLoadPV()
4827 {
4828   if(endPV < 0) return;
4829   endPV = -1;
4830   currentMove = forwardMostMove;
4831   ClearPremoveHighlights();
4832   DrawPosition(TRUE, boards[currentMove]);
4833 }
4834
4835 void
4836 MovePV(int x, int y, int h)
4837 { // step through PV based on mouse coordinates (called on mouse move)
4838   int margin = h>>3, step = 0;
4839
4840   if(endPV < 0) return;
4841   // we must somehow check if right button is still down (might be released off board!)
4842   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4843   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4844   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4845   if(!step) return;
4846   lastX = x; lastY = y;
4847   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4848   currentMove += step;
4849   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4850   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4851                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4852   DrawPosition(FALSE, boards[currentMove]);
4853 }
4854
4855
4856 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4857 // All positions will have equal probability, but the current method will not provide a unique
4858 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4859 #define DARK 1
4860 #define LITE 2
4861 #define ANY 3
4862
4863 int squaresLeft[4];
4864 int piecesLeft[(int)BlackPawn];
4865 int seed, nrOfShuffles;
4866
4867 void GetPositionNumber()
4868 {       // sets global variable seed
4869         int i;
4870
4871         seed = appData.defaultFrcPosition;
4872         if(seed < 0) { // randomize based on time for negative FRC position numbers
4873                 for(i=0; i<50; i++) seed += random();
4874                 seed = random() ^ random() >> 8 ^ random() << 8;
4875                 if(seed<0) seed = -seed;
4876         }
4877 }
4878
4879 int put(Board board, int pieceType, int rank, int n, int shade)
4880 // put the piece on the (n-1)-th empty squares of the given shade
4881 {
4882         int i;
4883
4884         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4885                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4886                         board[rank][i] = (ChessSquare) pieceType;
4887                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4888                         squaresLeft[ANY]--;
4889                         piecesLeft[pieceType]--;
4890                         return i;
4891                 }
4892         }
4893         return -1;
4894 }
4895
4896
4897 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4898 // calculate where the next piece goes, (any empty square), and put it there
4899 {
4900         int i;
4901
4902         i = seed % squaresLeft[shade];
4903         nrOfShuffles *= squaresLeft[shade];
4904         seed /= squaresLeft[shade];
4905         put(board, pieceType, rank, i, shade);
4906 }
4907
4908 void AddTwoPieces(Board board, int pieceType, int rank)
4909 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4910 {
4911         int i, n=squaresLeft[ANY], j=n-1, k;
4912
4913         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4914         i = seed % k;  // pick one
4915         nrOfShuffles *= k;
4916         seed /= k;
4917         while(i >= j) i -= j--;
4918         j = n - 1 - j; i += j;
4919         put(board, pieceType, rank, j, ANY);
4920         put(board, pieceType, rank, i, ANY);
4921 }
4922
4923 void SetUpShuffle(Board board, int number)
4924 {
4925         int i, p, first=1;
4926
4927         GetPositionNumber(); nrOfShuffles = 1;
4928
4929         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4930         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4931         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4932
4933         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4934
4935         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4936             p = (int) board[0][i];
4937             if(p < (int) BlackPawn) piecesLeft[p] ++;
4938             board[0][i] = EmptySquare;
4939         }
4940
4941         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4942             // shuffles restricted to allow normal castling put KRR first
4943             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4944                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4945             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4946                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4947             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4948                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4949             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4950                 put(board, WhiteRook, 0, 0, ANY);
4951             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4952         }
4953
4954         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4955             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4956             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4957                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4958                 while(piecesLeft[p] >= 2) {
4959                     AddOnePiece(board, p, 0, LITE);
4960                     AddOnePiece(board, p, 0, DARK);
4961                 }
4962                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4963             }
4964
4965         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4966             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4967             // but we leave King and Rooks for last, to possibly obey FRC restriction
4968             if(p == (int)WhiteRook) continue;
4969             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4970             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4971         }
4972
4973         // now everything is placed, except perhaps King (Unicorn) and Rooks
4974
4975         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4976             // Last King gets castling rights
4977             while(piecesLeft[(int)WhiteUnicorn]) {
4978                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4979                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4980             }
4981
4982             while(piecesLeft[(int)WhiteKing]) {
4983                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4984                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4985             }
4986
4987
4988         } else {
4989             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4990             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4991         }
4992
4993         // Only Rooks can be left; simply place them all
4994         while(piecesLeft[(int)WhiteRook]) {
4995                 i = put(board, WhiteRook, 0, 0, ANY);
4996                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4997                         if(first) {
4998                                 first=0;
4999                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5000                         }
5001                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5002                 }
5003         }
5004         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5005             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5006         }
5007
5008         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5009 }
5010
5011 int SetCharTable( char *table, const char * map )
5012 /* [HGM] moved here from winboard.c because of its general usefulness */
5013 /*       Basically a safe strcpy that uses the last character as King */
5014 {
5015     int result = FALSE; int NrPieces;
5016
5017     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5018                     && NrPieces >= 12 && !(NrPieces&1)) {
5019         int i; /* [HGM] Accept even length from 12 to 34 */
5020
5021         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5022         for( i=0; i<NrPieces/2-1; i++ ) {
5023             table[i] = map[i];
5024             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5025         }
5026         table[(int) WhiteKing]  = map[NrPieces/2-1];
5027         table[(int) BlackKing]  = map[NrPieces-1];
5028
5029         result = TRUE;
5030     }
5031
5032     return result;
5033 }
5034
5035 void Prelude(Board board)
5036 {       // [HGM] superchess: random selection of exo-pieces
5037         int i, j, k; ChessSquare p;
5038         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5039
5040         GetPositionNumber(); // use FRC position number
5041
5042         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5043             SetCharTable(pieceToChar, appData.pieceToCharTable);
5044             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5045                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5046         }
5047
5048         j = seed%4;                 seed /= 4;
5049         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5050         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5051         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5052         j = seed%3 + (seed%3 >= j); seed /= 3;
5053         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5054         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5055         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5056         j = seed%3;                 seed /= 3;
5057         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5058         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5059         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5060         j = seed%2 + (seed%2 >= j); seed /= 2;
5061         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5062         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5063         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5064         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5065         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5066         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5067         put(board, exoPieces[0],    0, 0, ANY);
5068         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5069 }
5070
5071 void
5072 InitPosition(redraw)
5073      int redraw;
5074 {
5075     ChessSquare (* pieces)[BOARD_FILES];
5076     int i, j, pawnRow, overrule,
5077     oldx = gameInfo.boardWidth,
5078     oldy = gameInfo.boardHeight,
5079     oldh = gameInfo.holdingsWidth,
5080     oldv = gameInfo.variant;
5081
5082     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5083
5084     /* [AS] Initialize pv info list [HGM] and game status */
5085     {
5086         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5087             pvInfoList[i].depth = 0;
5088             boards[i][EP_STATUS] = EP_NONE;
5089             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5090         }
5091
5092         initialRulePlies = 0; /* 50-move counter start */
5093
5094         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5095         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5096     }
5097
5098
5099     /* [HGM] logic here is completely changed. In stead of full positions */
5100     /* the initialized data only consist of the two backranks. The switch */
5101     /* selects which one we will use, which is than copied to the Board   */
5102     /* initialPosition, which for the rest is initialized by Pawns and    */
5103     /* empty squares. This initial position is then copied to boards[0],  */
5104     /* possibly after shuffling, so that it remains available.            */
5105
5106     gameInfo.holdingsWidth = 0; /* default board sizes */
5107     gameInfo.boardWidth    = 8;
5108     gameInfo.boardHeight   = 8;
5109     gameInfo.holdingsSize  = 0;
5110     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5111     for(i=0; i<BOARD_FILES-2; i++)
5112       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5113     initialPosition[EP_STATUS] = EP_NONE;
5114     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5115
5116     switch (gameInfo.variant) {
5117     case VariantFischeRandom:
5118       shuffleOpenings = TRUE;
5119     default:
5120       pieces = FIDEArray;
5121       break;
5122     case VariantShatranj:
5123       pieces = ShatranjArray;
5124       nrCastlingRights = 0;
5125       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5126       break;
5127     case VariantMakruk:
5128       pieces = makrukArray;
5129       nrCastlingRights = 0;
5130       startedFromSetupPosition = TRUE;
5131       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5132       break;
5133     case VariantTwoKings:
5134       pieces = twoKingsArray;
5135       break;
5136     case VariantCapaRandom:
5137       shuffleOpenings = TRUE;
5138     case VariantCapablanca:
5139       pieces = CapablancaArray;
5140       gameInfo.boardWidth = 10;
5141       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5142       break;
5143     case VariantGothic:
5144       pieces = GothicArray;
5145       gameInfo.boardWidth = 10;
5146       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5147       break;
5148     case VariantJanus:
5149       pieces = JanusArray;
5150       gameInfo.boardWidth = 10;
5151       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5152       nrCastlingRights = 6;
5153         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5154         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5155         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5156         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5157         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5158         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5159       break;
5160     case VariantFalcon:
5161       pieces = FalconArray;
5162       gameInfo.boardWidth = 10;
5163       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5164       break;
5165     case VariantXiangqi:
5166       pieces = XiangqiArray;
5167       gameInfo.boardWidth  = 9;
5168       gameInfo.boardHeight = 10;
5169       nrCastlingRights = 0;
5170       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5171       break;
5172     case VariantShogi:
5173       pieces = ShogiArray;
5174       gameInfo.boardWidth  = 9;
5175       gameInfo.boardHeight = 9;
5176       gameInfo.holdingsSize = 7;
5177       nrCastlingRights = 0;
5178       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5179       break;
5180     case VariantCourier:
5181       pieces = CourierArray;
5182       gameInfo.boardWidth  = 12;
5183       nrCastlingRights = 0;
5184       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5185       break;
5186     case VariantKnightmate:
5187       pieces = KnightmateArray;
5188       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5189       break;
5190     case VariantFairy:
5191       pieces = fairyArray;
5192       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5193       break;
5194     case VariantGreat:
5195       pieces = GreatArray;
5196       gameInfo.boardWidth = 10;
5197       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5198       gameInfo.holdingsSize = 8;
5199       break;
5200     case VariantSuper:
5201       pieces = FIDEArray;
5202       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5203       gameInfo.holdingsSize = 8;
5204       startedFromSetupPosition = TRUE;
5205       break;
5206     case VariantCrazyhouse:
5207     case VariantBughouse:
5208       pieces = FIDEArray;
5209       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5210       gameInfo.holdingsSize = 5;
5211       break;
5212     case VariantWildCastle:
5213       pieces = FIDEArray;
5214       /* !!?shuffle with kings guaranteed to be on d or e file */
5215       shuffleOpenings = 1;
5216       break;
5217     case VariantNoCastle:
5218       pieces = FIDEArray;
5219       nrCastlingRights = 0;
5220       /* !!?unconstrained back-rank shuffle */
5221       shuffleOpenings = 1;
5222       break;
5223     }
5224
5225     overrule = 0;
5226     if(appData.NrFiles >= 0) {
5227         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5228         gameInfo.boardWidth = appData.NrFiles;
5229     }
5230     if(appData.NrRanks >= 0) {
5231         gameInfo.boardHeight = appData.NrRanks;
5232     }
5233     if(appData.holdingsSize >= 0) {
5234         i = appData.holdingsSize;
5235         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5236         gameInfo.holdingsSize = i;
5237     }
5238     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5239     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5240         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5241
5242     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5243     if(pawnRow < 1) pawnRow = 1;
5244     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5245
5246     /* User pieceToChar list overrules defaults */
5247     if(appData.pieceToCharTable != NULL)
5248         SetCharTable(pieceToChar, appData.pieceToCharTable);
5249
5250     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5251
5252         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5253             s = (ChessSquare) 0; /* account holding counts in guard band */
5254         for( i=0; i<BOARD_HEIGHT; i++ )
5255             initialPosition[i][j] = s;
5256
5257         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5258         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5259         initialPosition[pawnRow][j] = WhitePawn;
5260         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5261         if(gameInfo.variant == VariantXiangqi) {
5262             if(j&1) {
5263                 initialPosition[pawnRow][j] =
5264                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5265                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5266                    initialPosition[2][j] = WhiteCannon;
5267                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5268                 }
5269             }
5270         }
5271         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5272     }
5273     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5274
5275             j=BOARD_LEFT+1;
5276             initialPosition[1][j] = WhiteBishop;
5277             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5278             j=BOARD_RGHT-2;
5279             initialPosition[1][j] = WhiteRook;
5280             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5281     }
5282
5283     if( nrCastlingRights == -1) {
5284         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5285         /*       This sets default castling rights from none to normal corners   */
5286         /* Variants with other castling rights must set them themselves above    */
5287         nrCastlingRights = 6;
5288         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5289         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5290         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5291         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5292         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5293         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5294      }
5295
5296      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5297      if(gameInfo.variant == VariantGreat) { // promotion commoners
5298         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5299         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5300         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5301         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5302      }
5303   if (appData.debugMode) {
5304     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5305   }
5306     if(shuffleOpenings) {
5307         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5308         startedFromSetupPosition = TRUE;
5309     }
5310     if(startedFromPositionFile) {
5311       /* [HGM] loadPos: use PositionFile for every new game */
5312       CopyBoard(initialPosition, filePosition);
5313       for(i=0; i<nrCastlingRights; i++)
5314           initialRights[i] = filePosition[CASTLING][i];
5315       startedFromSetupPosition = TRUE;
5316     }
5317
5318     CopyBoard(boards[0], initialPosition);
5319     if(oldx != gameInfo.boardWidth ||
5320        oldy != gameInfo.boardHeight ||
5321        oldh != gameInfo.holdingsWidth
5322 #ifdef GOTHIC
5323        || oldv == VariantGothic ||        // For licensing popups
5324        gameInfo.variant == VariantGothic
5325 #endif
5326 #ifdef FALCON
5327        || oldv == VariantFalcon ||
5328        gameInfo.variant == VariantFalcon
5329 #endif
5330                                          )
5331       {
5332             InitDrawingSizes(-2 ,0);
5333       }
5334
5335     if (redraw)
5336       DrawPosition(TRUE, boards[currentMove]);
5337
5338 }
5339
5340 void
5341 SendBoard(cps, moveNum)
5342      ChessProgramState *cps;
5343      int moveNum;
5344 {
5345     char message[MSG_SIZ];
5346
5347     if (cps->useSetboard) {
5348       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5349       sprintf(message, "setboard %s\n", fen);
5350       SendToProgram(message, cps);
5351       free(fen);
5352
5353     } else {
5354       ChessSquare *bp;
5355       int i, j;
5356       /* Kludge to set black to move, avoiding the troublesome and now
5357        * deprecated "black" command.
5358        */
5359       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5360
5361       SendToProgram("edit\n", cps);
5362       SendToProgram("#\n", cps);
5363       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5364         bp = &boards[moveNum][i][BOARD_LEFT];
5365         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5366           if ((int) *bp < (int) BlackPawn) {
5367             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5368                     AAA + j, ONE + i);
5369             if(message[0] == '+' || message[0] == '~') {
5370                 sprintf(message, "%c%c%c+\n",
5371                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5372                         AAA + j, ONE + i);
5373             }
5374             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5375                 message[1] = BOARD_RGHT   - 1 - j + '1';
5376                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5377             }
5378             SendToProgram(message, cps);
5379           }
5380         }
5381       }
5382
5383       SendToProgram("c\n", cps);
5384       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5385         bp = &boards[moveNum][i][BOARD_LEFT];
5386         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5387           if (((int) *bp != (int) EmptySquare)
5388               && ((int) *bp >= (int) BlackPawn)) {
5389             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5390                     AAA + j, ONE + i);
5391             if(message[0] == '+' || message[0] == '~') {
5392                 sprintf(message, "%c%c%c+\n",
5393                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5394                         AAA + j, ONE + i);
5395             }
5396             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5397                 message[1] = BOARD_RGHT   - 1 - j + '1';
5398                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5399             }
5400             SendToProgram(message, cps);
5401           }
5402         }
5403       }
5404
5405       SendToProgram(".\n", cps);
5406     }
5407     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5408 }
5409
5410 int
5411 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5412 {
5413     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5414     /* [HGM] add Shogi promotions */
5415     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5416     ChessSquare piece;
5417     ChessMove moveType;
5418     Boolean premove;
5419
5420     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5421     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5422
5423     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5424       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5425         return FALSE;
5426
5427     piece = boards[currentMove][fromY][fromX];
5428     if(gameInfo.variant == VariantShogi) {
5429         promotionZoneSize = 3;
5430         highestPromotingPiece = (int)WhiteFerz;
5431     } else if(gameInfo.variant == VariantMakruk) {
5432         promotionZoneSize = 3;
5433     }
5434
5435     // next weed out all moves that do not touch the promotion zone at all
5436     if((int)piece >= BlackPawn) {
5437         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5438              return FALSE;
5439         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5440     } else {
5441         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5442            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5443     }
5444
5445     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5446
5447     // weed out mandatory Shogi promotions
5448     if(gameInfo.variant == VariantShogi) {
5449         if(piece >= BlackPawn) {
5450             if(toY == 0 && piece == BlackPawn ||
5451                toY == 0 && piece == BlackQueen ||
5452                toY <= 1 && piece == BlackKnight) {
5453                 *promoChoice = '+';
5454                 return FALSE;
5455             }
5456         } else {
5457             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5458                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5459                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5460                 *promoChoice = '+';
5461                 return FALSE;
5462             }
5463         }
5464     }
5465
5466     // weed out obviously illegal Pawn moves
5467     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5468         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5469         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5470         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5471         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5472         // note we are not allowed to test for valid (non-)capture, due to premove
5473     }
5474
5475     // we either have a choice what to promote to, or (in Shogi) whether to promote
5476     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5477         *promoChoice = PieceToChar(BlackFerz);  // no choice
5478         return FALSE;
5479     }
5480     if(appData.alwaysPromoteToQueen) { // predetermined
5481         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5482              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5483         else *promoChoice = PieceToChar(BlackQueen);
5484         return FALSE;
5485     }
5486
5487     // suppress promotion popup on illegal moves that are not premoves
5488     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5489               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5490     if(appData.testLegality && !premove) {
5491         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5492                         fromY, fromX, toY, toX, NULLCHAR);
5493         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5494            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5495             return FALSE;
5496     }
5497
5498     return TRUE;
5499 }
5500
5501 int
5502 InPalace(row, column)
5503      int row, column;
5504 {   /* [HGM] for Xiangqi */
5505     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5506          column < (BOARD_WIDTH + 4)/2 &&
5507          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5508     return FALSE;
5509 }
5510
5511 int
5512 PieceForSquare (x, y)
5513      int x;
5514      int y;
5515 {
5516   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5517      return -1;
5518   else
5519      return boards[currentMove][y][x];
5520 }
5521
5522 int
5523 OKToStartUserMove(x, y)
5524      int x, y;
5525 {
5526     ChessSquare from_piece;
5527     int white_piece;
5528
5529     if (matchMode) return FALSE;
5530     if (gameMode == EditPosition) return TRUE;
5531
5532     if (x >= 0 && y >= 0)
5533       from_piece = boards[currentMove][y][x];
5534     else
5535       from_piece = EmptySquare;
5536
5537     if (from_piece == EmptySquare) return FALSE;
5538
5539     white_piece = (int)from_piece >= (int)WhitePawn &&
5540       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5541
5542     switch (gameMode) {
5543       case PlayFromGameFile:
5544       case AnalyzeFile:
5545       case TwoMachinesPlay:
5546       case EndOfGame:
5547         return FALSE;
5548
5549       case IcsObserving:
5550       case IcsIdle:
5551         return FALSE;
5552
5553       case MachinePlaysWhite:
5554       case IcsPlayingBlack:
5555         if (appData.zippyPlay) return FALSE;
5556         if (white_piece) {
5557             DisplayMoveError(_("You are playing Black"));
5558             return FALSE;
5559         }
5560         break;
5561
5562       case MachinePlaysBlack:
5563       case IcsPlayingWhite:
5564         if (appData.zippyPlay) return FALSE;
5565         if (!white_piece) {
5566             DisplayMoveError(_("You are playing White"));
5567             return FALSE;
5568         }
5569         break;
5570
5571       case EditGame:
5572         if (!white_piece && WhiteOnMove(currentMove)) {
5573             DisplayMoveError(_("It is White's turn"));
5574             return FALSE;
5575         }
5576         if (white_piece && !WhiteOnMove(currentMove)) {
5577             DisplayMoveError(_("It is Black's turn"));
5578             return FALSE;
5579         }
5580         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5581             /* Editing correspondence game history */
5582             /* Could disallow this or prompt for confirmation */
5583             cmailOldMove = -1;
5584         }
5585         break;
5586
5587       case BeginningOfGame:
5588         if (appData.icsActive) return FALSE;
5589         if (!appData.noChessProgram) {
5590             if (!white_piece) {
5591                 DisplayMoveError(_("You are playing White"));
5592                 return FALSE;
5593             }
5594         }
5595         break;
5596
5597       case Training:
5598         if (!white_piece && WhiteOnMove(currentMove)) {
5599             DisplayMoveError(_("It is White's turn"));
5600             return FALSE;
5601         }
5602         if (white_piece && !WhiteOnMove(currentMove)) {
5603             DisplayMoveError(_("It is Black's turn"));
5604             return FALSE;
5605         }
5606         break;
5607
5608       default:
5609       case IcsExamining:
5610         break;
5611     }
5612     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5613         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5614         && gameMode != AnalyzeFile && gameMode != Training) {
5615         DisplayMoveError(_("Displayed position is not current"));
5616         return FALSE;
5617     }
5618     return TRUE;
5619 }
5620
5621 Boolean
5622 OnlyMove(int *x, int *y) {
5623     DisambiguateClosure cl;
5624     if (appData.zippyPlay) return FALSE;
5625     switch(gameMode) {
5626       case MachinePlaysBlack:
5627       case IcsPlayingWhite:
5628       case BeginningOfGame:
5629         if(!WhiteOnMove(currentMove)) return FALSE;
5630         break;
5631       case MachinePlaysWhite:
5632       case IcsPlayingBlack:
5633         if(WhiteOnMove(currentMove)) return FALSE;
5634         break;
5635       default:
5636         return FALSE;
5637     }
5638     cl.pieceIn = EmptySquare; 
5639     cl.rfIn = *y;
5640     cl.ffIn = *x;
5641     cl.rtIn = -1;
5642     cl.ftIn = -1;
5643     cl.promoCharIn = NULLCHAR;
5644     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5645     if(cl.kind == NormalMove) {
5646       fromX = cl.ff;
5647       fromY = cl.rf;
5648       *x = cl.ft;
5649       *y = cl.rt;
5650       return TRUE;
5651     }
5652     if(cl.kind != ImpossibleMove) return FALSE;
5653     cl.pieceIn = EmptySquare;
5654     cl.rfIn = -1;
5655     cl.ffIn = -1;
5656     cl.rtIn = *y;
5657     cl.ftIn = *x;
5658     cl.promoCharIn = NULLCHAR;
5659     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5660     if(cl.kind == NormalMove) {
5661       fromX = cl.ff;
5662       fromY = cl.rf;
5663       *x = cl.ft;
5664       *y = cl.rt;
5665       return TRUE;
5666     }
5667     return FALSE;
5668 }
5669
5670 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5671 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5672 int lastLoadGameUseList = FALSE;
5673 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5674 ChessMove lastLoadGameStart = (ChessMove) 0;
5675
5676 ChessMove
5677 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5678      int fromX, fromY, toX, toY;
5679      int promoChar;
5680      Boolean captureOwn;
5681 {
5682     ChessMove moveType;
5683     ChessSquare pdown, pup;
5684
5685     /* Check if the user is playing in turn.  This is complicated because we
5686        let the user "pick up" a piece before it is his turn.  So the piece he
5687        tried to pick up may have been captured by the time he puts it down!
5688        Therefore we use the color the user is supposed to be playing in this
5689        test, not the color of the piece that is currently on the starting
5690        square---except in EditGame mode, where the user is playing both
5691        sides; fortunately there the capture race can't happen.  (It can
5692        now happen in IcsExamining mode, but that's just too bad.  The user
5693        will get a somewhat confusing message in that case.)
5694        */
5695
5696     switch (gameMode) {
5697       case PlayFromGameFile:
5698       case AnalyzeFile:
5699       case TwoMachinesPlay:
5700       case EndOfGame:
5701       case IcsObserving:
5702       case IcsIdle:
5703         /* We switched into a game mode where moves are not accepted,
5704            perhaps while the mouse button was down. */
5705         return ImpossibleMove;
5706
5707       case MachinePlaysWhite:
5708         /* User is moving for Black */
5709         if (WhiteOnMove(currentMove)) {
5710             DisplayMoveError(_("It is White's turn"));
5711             return ImpossibleMove;
5712         }
5713         break;
5714
5715       case MachinePlaysBlack:
5716         /* User is moving for White */
5717         if (!WhiteOnMove(currentMove)) {
5718             DisplayMoveError(_("It is Black's turn"));
5719             return ImpossibleMove;
5720         }
5721         break;
5722
5723       case EditGame:
5724       case IcsExamining:
5725       case BeginningOfGame:
5726       case AnalyzeMode:
5727       case Training:
5728         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5729             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5730             /* User is moving for Black */
5731             if (WhiteOnMove(currentMove)) {
5732                 DisplayMoveError(_("It is White's turn"));
5733                 return ImpossibleMove;
5734             }
5735         } else {
5736             /* User is moving for White */
5737             if (!WhiteOnMove(currentMove)) {
5738                 DisplayMoveError(_("It is Black's turn"));
5739                 return ImpossibleMove;
5740             }
5741         }
5742         break;
5743
5744       case IcsPlayingBlack:
5745         /* User is moving for Black */
5746         if (WhiteOnMove(currentMove)) {
5747             if (!appData.premove) {
5748                 DisplayMoveError(_("It is White's turn"));
5749             } else if (toX >= 0 && toY >= 0) {
5750                 premoveToX = toX;
5751                 premoveToY = toY;
5752                 premoveFromX = fromX;
5753                 premoveFromY = fromY;
5754                 premovePromoChar = promoChar;
5755                 gotPremove = 1;
5756                 if (appData.debugMode)
5757                     fprintf(debugFP, "Got premove: fromX %d,"
5758                             "fromY %d, toX %d, toY %d\n",
5759                             fromX, fromY, toX, toY);
5760             }
5761             return ImpossibleMove;
5762         }
5763         break;
5764
5765       case IcsPlayingWhite:
5766         /* User is moving for White */
5767         if (!WhiteOnMove(currentMove)) {
5768             if (!appData.premove) {
5769                 DisplayMoveError(_("It is Black's turn"));
5770             } else if (toX >= 0 && toY >= 0) {
5771                 premoveToX = toX;
5772                 premoveToY = toY;
5773                 premoveFromX = fromX;
5774                 premoveFromY = fromY;
5775                 premovePromoChar = promoChar;
5776                 gotPremove = 1;
5777                 if (appData.debugMode)
5778                     fprintf(debugFP, "Got premove: fromX %d,"
5779                             "fromY %d, toX %d, toY %d\n",
5780                             fromX, fromY, toX, toY);
5781             }
5782             return ImpossibleMove;
5783         }
5784         break;
5785
5786       default:
5787         break;
5788
5789       case EditPosition:
5790         /* EditPosition, empty square, or different color piece;
5791            click-click move is possible */
5792         if (toX == -2 || toY == -2) {
5793             boards[0][fromY][fromX] = EmptySquare;
5794             return AmbiguousMove;
5795         } else if (toX >= 0 && toY >= 0) {
5796             boards[0][toY][toX] = boards[0][fromY][fromX];
5797             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5798                 if(boards[0][fromY][0] != EmptySquare) {
5799                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5800                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5801                 }
5802             } else
5803             if(fromX == BOARD_RGHT+1) {
5804                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5805                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5806                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5807                 }
5808             } else
5809             boards[0][fromY][fromX] = EmptySquare;
5810             return AmbiguousMove;
5811         }
5812         return ImpossibleMove;
5813     }
5814
5815     if(toX < 0 || toY < 0) return ImpossibleMove;
5816     pdown = boards[currentMove][fromY][fromX];
5817     pup = boards[currentMove][toY][toX];
5818
5819     /* [HGM] If move started in holdings, it means a drop */
5820     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5821          if( pup != EmptySquare ) return ImpossibleMove;
5822          if(appData.testLegality) {
5823              /* it would be more logical if LegalityTest() also figured out
5824               * which drops are legal. For now we forbid pawns on back rank.
5825               * Shogi is on its own here...
5826               */
5827              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5828                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5829                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5830          }
5831          return WhiteDrop; /* Not needed to specify white or black yet */
5832     }
5833
5834
5835     /* [HGM] always test for legality, to get promotion info */
5836     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5837                                          fromY, fromX, toY, toX, promoChar);
5838     /* [HGM] but possibly ignore an IllegalMove result */
5839     if (appData.testLegality) {
5840         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5841             DisplayMoveError(_("Illegal move"));
5842             return ImpossibleMove;
5843         }
5844     }
5845
5846     return moveType;
5847     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5848        function is made into one that returns an OK move type if FinishMove
5849        should be called. This to give the calling driver routine the
5850        opportunity to finish the userMove input with a promotion popup,
5851        without bothering the user with this for invalid or illegal moves */
5852
5853 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5854 }
5855
5856 /* Common tail of UserMoveEvent and DropMenuEvent */
5857 int
5858 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5859      ChessMove moveType;
5860      int fromX, fromY, toX, toY;
5861      /*char*/int promoChar;
5862 {
5863   char *bookHit = 0;
5864
5865   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5866     {
5867       // [HGM] superchess: suppress promotions to non-available piece
5868       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5869       if(WhiteOnMove(currentMove))
5870         {
5871           if(!boards[currentMove][k][BOARD_WIDTH-2])
5872             return 0;
5873         }
5874       else
5875         {
5876           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5877             return 0;
5878         }
5879     }
5880   
5881   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5882      move type in caller when we know the move is a legal promotion */
5883   if(moveType == NormalMove && promoChar)
5884     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5885   
5886   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5887      move type in caller when we know the move is a legal promotion */
5888   if(moveType == NormalMove && promoChar)
5889     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5890   
5891   /* [HGM] convert drag-and-drop piece drops to standard form */
5892   if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5893     {
5894       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5895       if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5896                                     moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5897       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5898       if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5899       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5900       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5901       fromY = DROP_RANK;
5902     }
5903   
5904   /* [HGM] <popupFix> The following if has been moved here from
5905      UserMoveEvent(). Because it seemed to belong here (why not allow
5906      piece drops in training games?), and because it can only be
5907      performed after it is known to what we promote. */
5908   if (gameMode == Training) 
5909     {
5910       /* compare the move played on the board to the next move in the
5911        * game. If they match, display the move and the opponent's response.
5912        * If they don't match, display an error message.
5913        */
5914       int saveAnimate;
5915       Board testBoard;
5916       CopyBoard(testBoard, boards[currentMove]);
5917       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5918
5919       if (CompareBoards(testBoard, boards[currentMove+1]))
5920         {
5921           ForwardInner(currentMove+1);
5922
5923           /* Autoplay the opponent's response.
5924            * if appData.animate was TRUE when Training mode was entered,
5925            * the response will be animated.
5926            */
5927           saveAnimate = appData.animate;
5928           appData.animate = animateTraining;
5929           ForwardInner(currentMove+1);
5930           appData.animate = saveAnimate;
5931
5932           /* check for the end of the game */
5933           if (currentMove >= forwardMostMove)
5934             {
5935               gameMode = PlayFromGameFile;
5936               ModeHighlight();
5937               SetTrainingModeOff();
5938               DisplayInformation(_("End of game"));
5939             }
5940         }
5941       else
5942         {
5943           DisplayError(_("Incorrect move"), 0);
5944         }
5945       return 1;
5946     }
5947
5948   /* Ok, now we know that the move is good, so we can kill
5949      the previous line in Analysis Mode */
5950   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5951                                 && currentMove < forwardMostMove) {
5952     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5953   }
5954
5955   /* If we need the chess program but it's dead, restart it */
5956   ResurrectChessProgram();
5957
5958   /* A user move restarts a paused game*/
5959   if (pausing)
5960     PauseEvent();
5961
5962   thinkOutput[0] = NULLCHAR;
5963
5964   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5965
5966
5967  if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5968
5969 if (gameMode == BeginningOfGame)
5970     {
5971       if (appData.noChessProgram)
5972         {
5973           gameMode = EditGame;
5974           SetGameInfo();
5975         }
5976       else
5977         {
5978           char buf[MSG_SIZ];
5979           gameMode = MachinePlaysBlack;
5980           StartClocks();
5981           SetGameInfo();
5982           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5983           DisplayTitle(buf);
5984           if (first.sendName)
5985             {
5986               sprintf(buf, "name %s\n", gameInfo.white);
5987               SendToProgram(buf, &first);
5988             }
5989           StartClocks();
5990         }
5991       ModeHighlight();
5992
5993     }
5994
5995   /* Relay move to ICS or chess engine */
5996
5997   if (appData.icsActive) {
5998     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5999         gameMode == IcsExamining) {
6000       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6001         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6002         SendToICS("draw ");
6003         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6004       }
6005       // also send plain move, in case ICS does not understand atomic claims
6006       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6007       ics_user_moved = 1;
6008     }
6009   } else {
6010     if (first.sendTime && (gameMode == BeginningOfGame ||
6011                            gameMode == MachinePlaysWhite ||
6012                            gameMode == MachinePlaysBlack)) {
6013       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6014     }
6015     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6016          // [HGM] book: if program might be playing, let it use book
6017         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6018         first.maybeThinking = TRUE;
6019     } else SendMoveToProgram(forwardMostMove-1, &first);
6020     if (currentMove == cmailOldMove + 1) {
6021       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6022     }
6023     }
6024
6025   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6026
6027   switch (gameMode) 
6028     {
6029     case EditGame:
6030       switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) 
6031         {
6032         case MT_NONE:
6033         case MT_CHECK:
6034           break;
6035         case MT_CHECKMATE:
6036         case MT_STAINMATE:
6037           if (WhiteOnMove(currentMove)) {
6038             GameEnds(BlackWins, "Black mates", GE_PLAYER);
6039           } else {
6040             GameEnds(WhiteWins, "White mates", GE_PLAYER);
6041           }
6042           break;
6043         case MT_STALEMATE:
6044           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6045           break;
6046         }
6047       break;
6048       
6049     case MachinePlaysBlack:
6050     case MachinePlaysWhite:
6051       /* disable certain menu options while machine is thinking */
6052       SetMachineThinkingEnables();
6053       break;
6054       
6055     default:
6056       break;
6057     }
6058   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6059         
6060   if(bookHit)
6061     { // [HGM] book: simulate book reply
6062         static char bookMove[MSG_SIZ]; // a bit generous?
6063
6064
6065       programStats.nodes = programStats.depth = programStats.time =
6066         programStats.score = programStats.got_only_move = 0;
6067       sprintf(programStats.movelist, "%s (xbook)", bookHit);
6068
6069       strcpy(bookMove, "move ");
6070       strcat(bookMove, bookHit);
6071       HandleMachineMove(bookMove, &first);
6072     }
6073
6074   return 1;
6075 }
6076
6077 void
6078 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6079      int fromX, fromY, toX, toY;
6080      int promoChar;
6081 {
6082     /* [HGM] This routine was added to allow calling of its two logical
6083        parts from other modules in the old way. Before, UserMoveEvent()
6084        automatically called FinishMove() if the move was OK, and returned
6085        otherwise. I separated the two, in order to make it possible to
6086        slip a promotion popup in between. But that it always needs two
6087        calls, to the first part, (now called UserMoveTest() ), and to
6088        FinishMove if the first part succeeded. Calls that do not need
6089        to do anything in between, can call this routine the old way.
6090     */
6091   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6092   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6093   if(moveType == AmbiguousMove)
6094     DrawPosition(FALSE, boards[currentMove]);
6095   else if(moveType != ImpossibleMove && moveType != Comment)
6096     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6097 }
6098
6099 void
6100 PromoDialog(int h, int w, Board board, Boolean clearBoard)
6101 {       // dummy routine to mimic with pseudo-popup what front-end should do:
6102         // display a popup with h x w mini-board
6103         int i, j;
6104         DisplayMessage("Click on your piece of choice", "");
6105         DrawPosition(TRUE, board);
6106 }
6107
6108 int hTab[(int)EmptySquare/2+1] = { 1,1,1,1,1,1,2,1,2,3,2,3,3,3,2,3,4,3,3,4,4,3,4 };
6109 int wTab[(int)EmptySquare/2+1] = { 1,1,2,3,4,5,3,7,4,3,5,4,4,5,7,5,4,6,6,5,5,7,6 };
6110 Board promoBoard;
6111 int promotionChoice = 0;
6112
6113 void PromoPopUp(ChessSquare piece)
6114 {   // determine the layout of the piece-choice dialog
6115     int w, h, i, j, nr;
6116     ChessSquare list[EmptySquare];
6117
6118     if(gameInfo.variant == VariantShogi) {
6119         // non-Pawn promotes; must be shogi
6120         h = 1; w = 1; list[0] = piece;
6121         if(PieceToChar(PROMOTED piece) != '.') {
6122             // promoted version is enabled
6123             w = 2; list[1] = PROMOTED piece;
6124         }
6125     } else {
6126         // Pawn, promotes to any enabled other piece
6127         h = 1; w = nr = 0;
6128         for(i=1; i<EmptySquare/2; i++) {
6129             if(PieceToChar(piece+i) != '.' 
6130            && PieceToChar(PROMOTED piece + i) != '~' // suppress bughouse true pieces
6131                                                         ) {
6132                 list[w++] = piece+i; nr++;
6133             }
6134         }
6135         if(appData.testLegality && gameInfo.variant != VariantSuicide) nr--,w--; // remove King
6136         h = hTab[nr]; w = wTab[nr]; // factorize with nice ratio
6137         for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) promoBoard[i][j] = EmptySquare;
6138         for(i=0; i < nr; i++) promoBoard[BOARD_LEFT+i/w][i%w] = list[i]; // layout
6139     }
6140     promotionChoice = 2; // wait for click on board
6141     PromoDialog(h, w, promoBoard, FALSE);
6142 }
6143
6144 void
6145 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6146      Board board;
6147      int flags;
6148      ChessMove kind;
6149      int rf, ff, rt, ft;
6150      VOIDSTAR closure;
6151 {
6152     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6153     Markers *m = (Markers *) closure;
6154     if(rf == fromY && ff == fromX)
6155         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6156                          || kind == WhiteCapturesEnPassant
6157                          || kind == BlackCapturesEnPassant);
6158     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6159 }
6160
6161 void
6162 MarkTargetSquares(int clear)
6163 {
6164   int x, y;
6165   if(!appData.markers || !appData.highlightDragging || 
6166      !appData.testLegality || gameMode == EditPosition) return;
6167   if(clear) {
6168     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6169   } else {
6170     int capt = 0;
6171     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6172     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6173       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6174       if(capt)
6175       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6176     }
6177   }
6178   DrawPosition(TRUE, NULL);
6179 }
6180
6181 void LeftClick(ClickType clickType, int xPix, int yPix)
6182 {
6183     int x, y;
6184     Boolean saveAnimate;
6185     static int second = 0;
6186     char promoChoice = NULLCHAR;
6187
6188     if(appData.seekGraph && appData.icsActive && loggedOn &&
6189         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6190         SeekGraphClick(clickType, xPix, yPix, 0);
6191         return;
6192     }
6193
6194     if (clickType == Press) ErrorPopDown();
6195     MarkTargetSquares(1);
6196
6197     x = EventToSquare(xPix, BOARD_WIDTH);
6198     y = EventToSquare(yPix, BOARD_HEIGHT);
6199     if (!flipView && y >= 0) {
6200         y = BOARD_HEIGHT - 1 - y;
6201     }
6202     if (flipView && x >= 0) {
6203         x = BOARD_WIDTH - 1 - x;
6204     }
6205
6206     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6207         ChessSquare p = EmptySquare; Boolean inHoldings;
6208         if(clickType == Release) return; // ignore upclick of click-click destination
6209         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6210         inHoldings = gameInfo.holdingsWidth &&
6211                 (WhiteOnMove(currentMove) 
6212                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6213                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1);
6214             // click in right holdings, for determining promotion piece
6215         if(promotionChoice == 1 && inHoldings || promotionChoice == 2 && x >= BOARD_LEFT && x < BOARD_RGHT) {
6216             p = promoBoard[y][x];
6217             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6218             if(p != EmptySquare) {
6219                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6220                 fromX = fromY = -1;
6221                 promotionChoice = 0;
6222                 return;
6223             }
6224         }
6225         promotionChoice = 0; // only one chance: if click not OK it is interpreted as cancel
6226         DrawPosition(FALSE, boards[currentMove]);
6227         return;
6228     }
6229
6230     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6231     if(clickType == Press
6232             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6233               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6234               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6235         return;
6236
6237     if (fromX == -1) {
6238       if(!appData.oneClick || !OnlyMove(&x, &y)) {
6239         if (clickType == Press) {
6240             /* First square */
6241             if (OKToStartUserMove(x, y)) {
6242                 fromX = x;
6243                 fromY = y;
6244                 second = 0;
6245                 MarkTargetSquares(0);
6246                 DragPieceBegin(xPix, yPix);
6247                 if (appData.highlightDragging) {
6248                     SetHighlights(x, y, -1, -1);
6249                 }
6250             }
6251         }
6252         return;
6253       }
6254     }
6255
6256     /* fromX != -1 */
6257     if (clickType == Press && gameMode != EditPosition) {
6258         ChessSquare fromP;
6259         ChessSquare toP;
6260         int frc;
6261
6262         // ignore off-board to clicks
6263         if(y < 0 || x < 0) return;
6264
6265         /* Check if clicking again on the same color piece */
6266         fromP = boards[currentMove][fromY][fromX];
6267         toP = boards[currentMove][y][x];
6268         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6269         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6270              WhitePawn <= toP && toP <= WhiteKing &&
6271              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6272              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6273             (BlackPawn <= fromP && fromP <= BlackKing && 
6274              BlackPawn <= toP && toP <= BlackKing &&
6275              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6276              !(fromP == BlackKing && toP == BlackRook && frc))) {
6277             /* Clicked again on same color piece -- changed his mind */
6278             second = (x == fromX && y == fromY);
6279             if (appData.highlightDragging) {
6280                 SetHighlights(x, y, -1, -1);
6281             } else {
6282                 ClearHighlights();
6283             }
6284             if (OKToStartUserMove(x, y)) {
6285                 fromX = x;
6286                 fromY = y;
6287                 MarkTargetSquares(0);
6288                 DragPieceBegin(xPix, yPix);
6289             }
6290             return;
6291         }
6292         // ignore clicks on holdings
6293         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6294     }
6295
6296     if (clickType == Release && x == fromX && y == fromY) {
6297         DragPieceEnd(xPix, yPix);
6298         if (appData.animateDragging) {
6299             /* Undo animation damage if any */
6300             DrawPosition(FALSE, NULL);
6301         }
6302         if (second) {
6303             /* Second up/down in same square; just abort move */
6304             second = 0;
6305             fromX = fromY = -1;
6306             ClearHighlights();
6307             gotPremove = 0;
6308             ClearPremoveHighlights();
6309         } else {
6310             /* First upclick in same square; start click-click mode */
6311             SetHighlights(x, y, -1, -1);
6312         }
6313         return;
6314     }
6315
6316     /* we now have a different from- and (possibly off-board) to-square */
6317     /* Completed move */
6318     toX = x;
6319     toY = y;
6320     saveAnimate = appData.animate;
6321     if (clickType == Press) {
6322         /* Finish clickclick move */
6323         if (appData.animate || appData.highlightLastMove) {
6324             SetHighlights(fromX, fromY, toX, toY);
6325         } else {
6326             ClearHighlights();
6327         }
6328     } else {
6329         /* Finish drag move */
6330         if (appData.highlightLastMove) {
6331             SetHighlights(fromX, fromY, toX, toY);
6332         } else {
6333             ClearHighlights();
6334         }
6335         DragPieceEnd(xPix, yPix);
6336         /* Don't animate move and drag both */
6337         appData.animate = FALSE;
6338     }
6339
6340     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6341     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6342         ChessSquare piece = boards[currentMove][fromY][fromX];
6343         if(gameMode == EditPosition && piece != EmptySquare &&
6344            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6345             int n;
6346              
6347             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6348                 n = PieceToNumber(piece - (int)BlackPawn);
6349                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6350                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6351                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6352             } else
6353             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6354                 n = PieceToNumber(piece);
6355                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6356                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6357                 boards[currentMove][n][BOARD_WIDTH-2]++;
6358             }
6359             boards[currentMove][fromY][fromX] = EmptySquare;
6360         }
6361         ClearHighlights();
6362         fromX = fromY = -1;
6363         DrawPosition(TRUE, boards[currentMove]);
6364         return;
6365     }
6366
6367     // off-board moves should not be highlighted
6368     if(x < 0 || x < 0) ClearHighlights();
6369
6370     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6371         SetHighlights(fromX, fromY, toX, toY);
6372         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6373             // [HGM] super: promotion to captured piece selected from holdings
6374             ChessSquare p = boards[currentMove][fromY][fromX];
6375             promotionChoice = 1;
6376             CopyBoard(promoBoard, boards[currentMove]);
6377             // kludge follows to temporarily execute move on display, without promoting yet
6378             promoBoard[fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6379             promoBoard[toY][toX] = p;
6380             DrawPosition(FALSE, promoBoard);
6381             DisplayMessage("Click in holdings to choose piece", "");
6382             return;
6383         }
6384         CopyBoard(promoBoard, boards[currentMove]);
6385         PromoPopUp(boards[currentMove][fromY][fromX]);
6386     } else {
6387         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6388         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6389         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6390         fromX = fromY = -1;
6391     }
6392     appData.animate = saveAnimate;
6393     if (appData.animate || appData.animateDragging) {
6394         /* Undo animation damage if needed */
6395         DrawPosition(FALSE, NULL);
6396     }
6397 }
6398
6399 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6400 {   // front-end-free part taken out of PieceMenuPopup
6401     int whichMenu; int xSqr, ySqr;
6402
6403     if(seekGraphUp) { // [HGM] seekgraph
6404         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6405         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6406         return -2;
6407     }
6408
6409     xSqr = EventToSquare(x, BOARD_WIDTH);
6410     ySqr = EventToSquare(y, BOARD_HEIGHT);
6411     if (action == Release) UnLoadPV(); // [HGM] pv
6412     if (action != Press) return -2; // return code to be ignored
6413     switch (gameMode) {
6414       case IcsExamining:
6415         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
6416       case EditPosition:
6417         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
6418         if (xSqr < 0 || ySqr < 0) return -1;
6419         whichMenu = 0; // edit-position menu
6420         break;
6421       case IcsObserving:
6422         if(!appData.icsEngineAnalyze) return -1;
6423       case IcsPlayingWhite:
6424       case IcsPlayingBlack:
6425         if(!appData.zippyPlay) goto noZip;
6426       case AnalyzeMode:
6427       case AnalyzeFile:
6428       case MachinePlaysWhite:
6429       case MachinePlaysBlack:
6430       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6431         if (!appData.dropMenu) {
6432           LoadPV(x, y);
6433           return 2; // flag front-end to grab mouse events
6434         }
6435         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6436            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6437       case EditGame:
6438       noZip:
6439         if (xSqr < 0 || ySqr < 0) return -1;
6440         if (!appData.dropMenu || appData.testLegality &&
6441             gameInfo.variant != VariantBughouse &&
6442             gameInfo.variant != VariantCrazyhouse) return -1;
6443         whichMenu = 1; // drop menu
6444         break;
6445       default:
6446         return -1;
6447     }
6448
6449     if (((*fromX = xSqr) < 0) ||
6450         ((*fromY = ySqr) < 0)) {
6451         *fromX = *fromY = -1;
6452         return -1;
6453     }
6454     if (flipView)
6455       *fromX = BOARD_WIDTH - 1 - *fromX;
6456     else
6457       *fromY = BOARD_HEIGHT - 1 - *fromY;
6458
6459     return whichMenu;
6460 }
6461
6462 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6463 {
6464 //    char * hint = lastHint;
6465     FrontEndProgramStats stats;
6466
6467     stats.which = cps == &first ? 0 : 1;
6468     stats.depth = cpstats->depth;
6469     stats.nodes = cpstats->nodes;
6470     stats.score = cpstats->score;
6471     stats.time = cpstats->time;
6472     stats.pv = cpstats->movelist;
6473     stats.hint = lastHint;
6474     stats.an_move_index = 0;
6475     stats.an_move_count = 0;
6476
6477     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6478         stats.hint = cpstats->move_name;
6479         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6480         stats.an_move_count = cpstats->nr_moves;
6481     }
6482
6483     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6484
6485     SetProgramStats( &stats );
6486 }
6487
6488 int
6489 Adjudicate(ChessProgramState *cps)
6490 {       // [HGM] some adjudications useful with buggy engines
6491         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6492         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6493         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6494         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6495         int k, count = 0; static int bare = 1;
6496         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6497         Boolean canAdjudicate = !appData.icsActive;
6498
6499         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6500         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6501             if( appData.testLegality )
6502             {   /* [HGM] Some more adjudications for obstinate engines */
6503                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6504                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6505                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6506                 static int moveCount = 6;
6507                 ChessMove result;
6508                 char *reason = NULL;
6509
6510
6511                 /* Count what is on board. */
6512                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6513                 {   ChessSquare p = boards[forwardMostMove][i][j];
6514                     int m=i;
6515
6516                     switch((int) p)
6517                     {   /* count B,N,R and other of each side */
6518                         case WhiteKing:
6519                         case BlackKing:
6520                              NrK++; break; // [HGM] atomic: count Kings
6521                         case WhiteKnight:
6522                              NrWN++; break;
6523                         case WhiteBishop:
6524                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6525                              bishopsColor |= 1 << ((i^j)&1);
6526                              NrWB++; break;
6527                         case BlackKnight:
6528                              NrBN++; break;
6529                         case BlackBishop:
6530                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6531                              bishopsColor |= 1 << ((i^j)&1);
6532                              NrBB++; break;
6533                         case WhiteRook:
6534                              NrWR++; break;
6535                         case BlackRook:
6536                              NrBR++; break;
6537                         case WhiteQueen:
6538                              NrWQ++; break;
6539                         case BlackQueen:
6540                              NrBQ++; break;
6541                         case EmptySquare: 
6542                              break;
6543                         case BlackPawn:
6544                              m = 7-i;
6545                         case WhitePawn:
6546                              PawnAdvance += m; NrPawns++;
6547                     }
6548                     NrPieces += (p != EmptySquare);
6549                     NrW += ((int)p < (int)BlackPawn);
6550                     if(gameInfo.variant == VariantXiangqi && 
6551                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6552                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6553                         NrW -= ((int)p < (int)BlackPawn);
6554                     }
6555                 }
6556
6557                 /* Some material-based adjudications that have to be made before stalemate test */
6558                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6559                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6560                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6561                      if(canAdjudicate && appData.checkMates) {
6562                          if(engineOpponent)
6563                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6564                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6565                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6566                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6567                          return 1;
6568                      }
6569                 }
6570
6571                 /* Bare King in Shatranj (loses) or Losers (wins) */
6572                 if( NrW == 1 || NrPieces - NrW == 1) {
6573                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6574                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6575                      if(canAdjudicate && appData.checkMates) {
6576                          if(engineOpponent)
6577                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6578                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6579                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6580                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6581                          return 1;
6582                      }
6583                   } else
6584                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6585                   {    /* bare King */
6586                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6587                         if(canAdjudicate && appData.checkMates) {
6588                             /* but only adjudicate if adjudication enabled */
6589                             if(engineOpponent)
6590                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6591                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6592                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6593                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6594                             return 1;
6595                         }
6596                   }
6597                 } else bare = 1;
6598
6599
6600             // don't wait for engine to announce game end if we can judge ourselves
6601             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6602               case MT_CHECK:
6603                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6604                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6605                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6606                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6607                             checkCnt++;
6608                         if(checkCnt >= 2) {
6609                             reason = "Xboard adjudication: 3rd check";
6610                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6611                             break;
6612                         }
6613                     }
6614                 }
6615               case MT_NONE:
6616               default:
6617                 break;
6618               case MT_STALEMATE:
6619               case MT_STAINMATE:
6620                 reason = "Xboard adjudication: Stalemate";
6621                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6622                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6623                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6624                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6625                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6626                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6627                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6628                                                                         EP_CHECKMATE : EP_WINS);
6629                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6630                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6631                 }
6632                 break;
6633               case MT_CHECKMATE:
6634                 reason = "Xboard adjudication: Checkmate";
6635                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6636                 break;
6637             }
6638
6639                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6640                     case EP_STALEMATE:
6641                         result = GameIsDrawn; break;
6642                     case EP_CHECKMATE:
6643                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6644                     case EP_WINS:
6645                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6646                     default:
6647                         result = (ChessMove) 0;
6648                 }
6649                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6650                     if(engineOpponent)
6651                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6652                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6653                     GameEnds( result, reason, GE_XBOARD );
6654                     return 1;
6655                 }
6656
6657                 /* Next absolutely insufficient mating material. */
6658                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6659                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6660                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6661                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6662                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6663
6664                      /* always flag draws, for judging claims */
6665                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6666
6667                      if(canAdjudicate && appData.materialDraws) {
6668                          /* but only adjudicate them if adjudication enabled */
6669                          if(engineOpponent) {
6670                            SendToProgram("force\n", engineOpponent); // suppress reply
6671                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6672                          }
6673                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6674                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6675                          return 1;
6676                      }
6677                 }
6678
6679                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6680                 if(NrPieces == 4 && 
6681                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6682                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6683                    || NrWN==2 || NrBN==2     /* KNNK */
6684                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6685                   ) ) {
6686                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6687                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6688                           if(engineOpponent) {
6689                             SendToProgram("force\n", engineOpponent); // suppress reply
6690                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6691                           }
6692                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6693                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6694                           return 1;
6695                      }
6696                 } else moveCount = 6;
6697             }
6698         }
6699           
6700         if (appData.debugMode) { int i;
6701             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6702                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6703                     appData.drawRepeats);
6704             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6705               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6706             
6707         }
6708
6709         // Repetition draws and 50-move rule can be applied independently of legality testing
6710
6711                 /* Check for rep-draws */
6712                 count = 0;
6713                 for(k = forwardMostMove-2;
6714                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6715                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6716                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6717                     k-=2)
6718                 {   int rights=0;
6719                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6720                         /* compare castling rights */
6721                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6722                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6723                                 rights++; /* King lost rights, while rook still had them */
6724                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6725                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6726                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6727                                    rights++; /* but at least one rook lost them */
6728                         }
6729                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6730                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6731                                 rights++; 
6732                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6733                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6734                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6735                                    rights++;
6736                         }
6737                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6738                             && appData.drawRepeats > 1) {
6739                              /* adjudicate after user-specified nr of repeats */
6740                              if(engineOpponent) {
6741                                SendToProgram("force\n", engineOpponent); // suppress reply
6742                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6743                              }
6744                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6745                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6746                                 // [HGM] xiangqi: check for forbidden perpetuals
6747                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6748                                 for(m=forwardMostMove; m>k; m-=2) {
6749                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6750                                         ourPerpetual = 0; // the current mover did not always check
6751                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6752                                         hisPerpetual = 0; // the opponent did not always check
6753                                 }
6754                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6755                                                                         ourPerpetual, hisPerpetual);
6756                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6757                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6758                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6759                                     return 1;
6760                                 }
6761                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6762                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6763                                 // Now check for perpetual chases
6764                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6765                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6766                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6767                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6768                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6769                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6770                                         return 1;
6771                                     }
6772                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6773                                         break; // Abort repetition-checking loop.
6774                                 }
6775                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6776                              }
6777                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6778                              return 1;
6779                         }
6780                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6781                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6782                     }
6783                 }
6784
6785                 /* Now we test for 50-move draws. Determine ply count */
6786                 count = forwardMostMove;
6787                 /* look for last irreversble move */
6788                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6789                     count--;
6790                 /* if we hit starting position, add initial plies */
6791                 if( count == backwardMostMove )
6792                     count -= initialRulePlies;
6793                 count = forwardMostMove - count; 
6794                 if( count >= 100)
6795                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6796                          /* this is used to judge if draw claims are legal */
6797                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6798                          if(engineOpponent) {
6799                            SendToProgram("force\n", engineOpponent); // suppress reply
6800                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6801                          }
6802                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6803                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6804                          return 1;
6805                 }
6806
6807                 /* if draw offer is pending, treat it as a draw claim
6808                  * when draw condition present, to allow engines a way to
6809                  * claim draws before making their move to avoid a race
6810                  * condition occurring after their move
6811                  */
6812                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6813                          char *p = NULL;
6814                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6815                              p = "Draw claim: 50-move rule";
6816                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6817                              p = "Draw claim: 3-fold repetition";
6818                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6819                              p = "Draw claim: insufficient mating material";
6820                          if( p != NULL && canAdjudicate) {
6821                              if(engineOpponent) {
6822                                SendToProgram("force\n", engineOpponent); // suppress reply
6823                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6824                              }
6825                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6826                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6827                              return 1;
6828                          }
6829                 }
6830
6831                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6832                     if(engineOpponent) {
6833                       SendToProgram("force\n", engineOpponent); // suppress reply
6834                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6835                     }
6836                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6837                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6838                     return 1;
6839                 }
6840         return 0;
6841 }
6842
6843 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6844 {   // [HGM] book: this routine intercepts moves to simulate book replies
6845     char *bookHit = NULL;
6846
6847     //first determine if the incoming move brings opponent into his book
6848     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6849         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6850     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6851     if(bookHit != NULL && !cps->bookSuspend) {
6852         // make sure opponent is not going to reply after receiving move to book position
6853         SendToProgram("force\n", cps);
6854         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6855     }
6856     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6857     // now arrange restart after book miss
6858     if(bookHit) {
6859         // after a book hit we never send 'go', and the code after the call to this routine
6860         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6861         char buf[MSG_SIZ];
6862         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6863         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6864         SendToProgram(buf, cps);
6865         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6866     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6867         SendToProgram("go\n", cps);
6868         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6869     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6870         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6871             SendToProgram("go\n", cps); 
6872         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6873     }
6874     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6875 }
6876
6877 char *savedMessage;
6878 ChessProgramState *savedState;
6879 void DeferredBookMove(void)
6880 {
6881         if(savedState->lastPing != savedState->lastPong)
6882                     ScheduleDelayedEvent(DeferredBookMove, 10);
6883         else
6884         HandleMachineMove(savedMessage, savedState);
6885 }
6886
6887 void
6888 HandleMachineMove(message, cps)
6889      char *message;
6890      ChessProgramState *cps;
6891 {
6892     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6893     char realname[MSG_SIZ];
6894     int fromX, fromY, toX, toY;
6895     ChessMove moveType;
6896     char promoChar;
6897     char *p;
6898     int machineWhite;
6899     char *bookHit;
6900
6901     cps->userError = 0;
6902
6903 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6904     /*
6905      * Kludge to ignore BEL characters
6906      */
6907     while (*message == '\007') message++;
6908
6909     /*
6910      * [HGM] engine debug message: ignore lines starting with '#' character
6911      */
6912     if(cps->debug && *message == '#') return;
6913
6914     /*
6915      * Look for book output
6916      */
6917     if (cps == &first && bookRequested) {
6918         if (message[0] == '\t' || message[0] == ' ') {
6919             /* Part of the book output is here; append it */
6920             strcat(bookOutput, message);
6921             strcat(bookOutput, "  \n");
6922             return;
6923         } else if (bookOutput[0] != NULLCHAR) {
6924             /* All of book output has arrived; display it */
6925             char *p = bookOutput;
6926             while (*p != NULLCHAR) {
6927                 if (*p == '\t') *p = ' ';
6928                 p++;
6929             }
6930             DisplayInformation(bookOutput);
6931             bookRequested = FALSE;
6932             /* Fall through to parse the current output */
6933         }
6934     }
6935
6936     /*
6937      * Look for machine move.
6938      */
6939     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6940         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6941     {
6942         /* This method is only useful on engines that support ping */
6943         if (cps->lastPing != cps->lastPong) {
6944           if (gameMode == BeginningOfGame) {
6945             /* Extra move from before last new; ignore */
6946             if (appData.debugMode) {
6947                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6948             }
6949           } else {
6950             if (appData.debugMode) {
6951                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6952                         cps->which, gameMode);
6953             }
6954
6955             SendToProgram("undo\n", cps);
6956           }
6957           return;
6958         }
6959
6960         switch (gameMode) {
6961           case BeginningOfGame:
6962             /* Extra move from before last reset; ignore */
6963             if (appData.debugMode) {
6964                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6965             }
6966             return;
6967
6968           case EndOfGame:
6969           case IcsIdle:
6970           default:
6971             /* Extra move after we tried to stop.  The mode test is
6972                not a reliable way of detecting this problem, but it's
6973                the best we can do on engines that don't support ping.
6974             */
6975             if (appData.debugMode) {
6976                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6977                         cps->which, gameMode);
6978             }
6979             SendToProgram("undo\n", cps);
6980             return;
6981
6982           case MachinePlaysWhite:
6983           case IcsPlayingWhite:
6984             machineWhite = TRUE;
6985             break;
6986
6987           case MachinePlaysBlack:
6988           case IcsPlayingBlack:
6989             machineWhite = FALSE;
6990             break;
6991
6992           case TwoMachinesPlay:
6993             machineWhite = (cps->twoMachinesColor[0] == 'w');
6994             break;
6995         }
6996         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6997             if (appData.debugMode) {
6998                 fprintf(debugFP,
6999                         "Ignoring move out of turn by %s, gameMode %d"
7000                         ", forwardMost %d\n",
7001                         cps->which, gameMode, forwardMostMove);
7002             }
7003             return;
7004         }
7005
7006     if (appData.debugMode) { int f = forwardMostMove;
7007         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7008                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7009                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7010     }
7011         if(cps->alphaRank) AlphaRank(machineMove, 4);
7012         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7013                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7014             /* Machine move could not be parsed; ignore it. */
7015             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7016                     machineMove, cps->which);
7017             DisplayError(buf1, 0);
7018             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7019                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7020             if (gameMode == TwoMachinesPlay) {
7021               GameEnds(machineWhite ? BlackWins : WhiteWins,
7022                        buf1, GE_XBOARD);
7023             }
7024             return;
7025         }
7026
7027         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7028         /* So we have to redo legality test with true e.p. status here,  */
7029         /* to make sure an illegal e.p. capture does not slip through,   */
7030         /* to cause a forfeit on a justified illegal-move complaint      */
7031         /* of the opponent.                                              */
7032         if( gameMode==TwoMachinesPlay && appData.testLegality
7033             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7034                                                               ) {
7035            ChessMove moveType;
7036            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7037                              fromY, fromX, toY, toX, promoChar);
7038             if (appData.debugMode) {
7039                 int i;
7040                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7041                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7042                 fprintf(debugFP, "castling rights\n");
7043             }
7044             if(moveType == IllegalMove) {
7045                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7046                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7047                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7048                            buf1, GE_XBOARD);
7049                 return;
7050            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7051            /* [HGM] Kludge to handle engines that send FRC-style castling
7052               when they shouldn't (like TSCP-Gothic) */
7053            switch(moveType) {
7054              case WhiteASideCastleFR:
7055              case BlackASideCastleFR:
7056                toX+=2;
7057                currentMoveString[2]++;
7058                break;
7059              case WhiteHSideCastleFR:
7060              case BlackHSideCastleFR:
7061                toX--;
7062                currentMoveString[2]--;
7063                break;
7064              default: ; // nothing to do, but suppresses warning of pedantic compilers
7065            }
7066         }
7067         hintRequested = FALSE;
7068         lastHint[0] = NULLCHAR;
7069         bookRequested = FALSE;
7070         /* Program may be pondering now */
7071         cps->maybeThinking = TRUE;
7072         if (cps->sendTime == 2) cps->sendTime = 1;
7073         if (cps->offeredDraw) cps->offeredDraw--;
7074
7075         /* currentMoveString is set as a side-effect of ParseOneMove */
7076         strcpy(machineMove, currentMoveString);
7077         strcat(machineMove, "\n");
7078         strcpy(moveList[forwardMostMove], machineMove);
7079
7080         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7081
7082         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7083         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7084             int count = 0;
7085
7086             while( count < adjudicateLossPlies ) {
7087                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7088
7089                 if( count & 1 ) {
7090                     score = -score; /* Flip score for winning side */
7091                 }
7092
7093                 if( score > adjudicateLossThreshold ) {
7094                     break;
7095                 }
7096
7097                 count++;
7098             }
7099
7100             if( count >= adjudicateLossPlies ) {
7101                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7102
7103                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7104                     "Xboard adjudication", 
7105                     GE_XBOARD );
7106
7107                 return;
7108             }
7109         }
7110
7111         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7112
7113 #if ZIPPY
7114         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7115             first.initDone) {
7116           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7117                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7118                 SendToICS("draw ");
7119                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7120           }
7121           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7122           ics_user_moved = 1;
7123           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7124                 char buf[3*MSG_SIZ];
7125
7126                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7127                         programStats.score / 100.,
7128                         programStats.depth,
7129                         programStats.time / 100.,
7130                         (unsigned int)programStats.nodes,
7131                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7132                         programStats.movelist);
7133                 SendToICS(buf);
7134 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7135           }
7136         }
7137 #endif
7138
7139         /* [AS] Save move info and clear stats for next move */
7140         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7141         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7142         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7143         ClearProgramStats();
7144         thinkOutput[0] = NULLCHAR;
7145         hiddenThinkOutputState = 0;
7146
7147         bookHit = NULL;
7148         if (gameMode == TwoMachinesPlay) {
7149             /* [HGM] relaying draw offers moved to after reception of move */
7150             /* and interpreting offer as claim if it brings draw condition */
7151             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7152                 SendToProgram("draw\n", cps->other);
7153             }
7154             if (cps->other->sendTime) {
7155                 SendTimeRemaining(cps->other,
7156                                   cps->other->twoMachinesColor[0] == 'w');
7157             }
7158             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7159             if (firstMove && !bookHit) {
7160                 firstMove = FALSE;
7161                 if (cps->other->useColors) {
7162                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7163                 }
7164                 SendToProgram("go\n", cps->other);
7165             }
7166             cps->other->maybeThinking = TRUE;
7167         }
7168
7169         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7170
7171         if (!pausing && appData.ringBellAfterMoves) {
7172             RingBell();
7173         }
7174
7175         /*
7176          * Reenable menu items that were disabled while
7177          * machine was thinking
7178          */
7179         if (gameMode != TwoMachinesPlay)
7180             SetUserThinkingEnables();
7181
7182         // [HGM] book: after book hit opponent has received move and is now in force mode
7183         // force the book reply into it, and then fake that it outputted this move by jumping
7184         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7185         if(bookHit) {
7186                 static char bookMove[MSG_SIZ]; // a bit generous?
7187
7188                 strcpy(bookMove, "move ");
7189                 strcat(bookMove, bookHit);
7190                 message = bookMove;
7191                 cps = cps->other;
7192                 programStats.nodes = programStats.depth = programStats.time =
7193                 programStats.score = programStats.got_only_move = 0;
7194                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7195
7196                 if(cps->lastPing != cps->lastPong) {
7197                     savedMessage = message; // args for deferred call
7198                     savedState = cps;
7199                     ScheduleDelayedEvent(DeferredBookMove, 10);
7200                     return;
7201                 }
7202                 goto FakeBookMove;
7203         }
7204
7205         return;
7206     }
7207
7208     /* Set special modes for chess engines.  Later something general
7209      *  could be added here; for now there is just one kludge feature,
7210      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7211      *  when "xboard" is given as an interactive command.
7212      */
7213     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7214         cps->useSigint = FALSE;
7215         cps->useSigterm = FALSE;
7216     }
7217     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7218       ParseFeatures(message+8, cps);
7219       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7220     }
7221
7222     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7223      * want this, I was asked to put it in, and obliged.
7224      */
7225     if (!strncmp(message, "setboard ", 9)) {
7226         Board initial_position;
7227
7228         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7229
7230         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7231             DisplayError(_("Bad FEN received from engine"), 0);
7232             return ;
7233         } else {
7234            Reset(TRUE, FALSE);
7235            CopyBoard(boards[0], initial_position);
7236            initialRulePlies = FENrulePlies;
7237            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7238            else gameMode = MachinePlaysBlack;
7239            DrawPosition(FALSE, boards[currentMove]);
7240         }
7241         return;
7242     }
7243
7244     /*
7245      * Look for communication commands
7246      */
7247     if (!strncmp(message, "telluser ", 9)) {
7248         DisplayNote(message + 9);
7249         return;
7250     }
7251     if (!strncmp(message, "tellusererror ", 14)) {
7252         cps->userError = 1;
7253         DisplayError(message + 14, 0);
7254         return;
7255     }
7256     if (!strncmp(message, "tellopponent ", 13)) {
7257       if (appData.icsActive) {
7258         if (loggedOn) {
7259           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7260           SendToICS(buf1);
7261         }
7262       } else {
7263         DisplayNote(message + 13);
7264       }
7265       return;
7266     }
7267     if (!strncmp(message, "tellothers ", 11)) {
7268       if (appData.icsActive) {
7269         if (loggedOn) {
7270           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7271           SendToICS(buf1);
7272         }
7273       }
7274       return;
7275     }
7276     if (!strncmp(message, "tellall ", 8)) {
7277       if (appData.icsActive) {
7278         if (loggedOn) {
7279           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7280           SendToICS(buf1);
7281         }
7282       } else {
7283         DisplayNote(message + 8);
7284       }
7285       return;
7286     }
7287     if (strncmp(message, "warning", 7) == 0) {
7288         /* Undocumented feature, use tellusererror in new code */
7289         DisplayError(message, 0);
7290         return;
7291     }
7292     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7293         strcpy(realname, cps->tidy);
7294         strcat(realname, " query");
7295         AskQuestion(realname, buf2, buf1, cps->pr);
7296         return;
7297     }
7298     /* Commands from the engine directly to ICS.  We don't allow these to be
7299      *  sent until we are logged on. Crafty kibitzes have been known to
7300      *  interfere with the login process.
7301      */
7302     if (loggedOn) {
7303         if (!strncmp(message, "tellics ", 8)) {
7304             SendToICS(message + 8);
7305             SendToICS("\n");
7306             return;
7307         }
7308         if (!strncmp(message, "tellicsnoalias ", 15)) {
7309             SendToICS(ics_prefix);
7310             SendToICS(message + 15);
7311             SendToICS("\n");
7312             return;
7313         }
7314         /* The following are for backward compatibility only */
7315         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7316             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7317             SendToICS(ics_prefix);
7318             SendToICS(message);
7319             SendToICS("\n");
7320             return;
7321         }
7322     }
7323     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7324         return;
7325     }
7326     /*
7327      * If the move is illegal, cancel it and redraw the board.
7328      * Also deal with other error cases.  Matching is rather loose
7329      * here to accommodate engines written before the spec.
7330      */
7331     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7332         strncmp(message, "Error", 5) == 0) {
7333         if (StrStr(message, "name") ||
7334             StrStr(message, "rating") || StrStr(message, "?") ||
7335             StrStr(message, "result") || StrStr(message, "board") ||
7336             StrStr(message, "bk") || StrStr(message, "computer") ||
7337             StrStr(message, "variant") || StrStr(message, "hint") ||
7338             StrStr(message, "random") || StrStr(message, "depth") ||
7339             StrStr(message, "accepted")) {
7340             return;
7341         }
7342         if (StrStr(message, "protover")) {
7343           /* Program is responding to input, so it's apparently done
7344              initializing, and this error message indicates it is
7345              protocol version 1.  So we don't need to wait any longer
7346              for it to initialize and send feature commands. */
7347           FeatureDone(cps, 1);
7348           cps->protocolVersion = 1;
7349           return;
7350         }
7351         cps->maybeThinking = FALSE;
7352
7353         if (StrStr(message, "draw")) {
7354             /* Program doesn't have "draw" command */
7355             cps->sendDrawOffers = 0;
7356             return;
7357         }
7358         if (cps->sendTime != 1 &&
7359             (StrStr(message, "time") || StrStr(message, "otim"))) {
7360           /* Program apparently doesn't have "time" or "otim" command */
7361           cps->sendTime = 0;
7362           return;
7363         }
7364         if (StrStr(message, "analyze")) {
7365             cps->analysisSupport = FALSE;
7366             cps->analyzing = FALSE;
7367             Reset(FALSE, TRUE);
7368             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7369             DisplayError(buf2, 0);
7370             return;
7371         }
7372         if (StrStr(message, "(no matching move)st")) {
7373           /* Special kludge for GNU Chess 4 only */
7374           cps->stKludge = TRUE;
7375           SendTimeControl(cps, movesPerSession, timeControl,
7376                           timeIncrement, appData.searchDepth,
7377                           searchTime);
7378           return;
7379         }
7380         if (StrStr(message, "(no matching move)sd")) {
7381           /* Special kludge for GNU Chess 4 only */
7382           cps->sdKludge = TRUE;
7383           SendTimeControl(cps, movesPerSession, timeControl,
7384                           timeIncrement, appData.searchDepth,
7385                           searchTime);
7386           return;
7387         }
7388         if (!StrStr(message, "llegal")) {
7389             return;
7390         }
7391         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7392             gameMode == IcsIdle) return;
7393         if (forwardMostMove <= backwardMostMove) return;
7394         if (pausing) PauseEvent();
7395       if(appData.forceIllegal) {
7396             // [HGM] illegal: machine refused move; force position after move into it
7397           SendToProgram("force\n", cps);
7398           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7399                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7400                 // when black is to move, while there might be nothing on a2 or black
7401                 // might already have the move. So send the board as if white has the move.
7402                 // But first we must change the stm of the engine, as it refused the last move
7403                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7404                 if(WhiteOnMove(forwardMostMove)) {
7405                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7406                     SendBoard(cps, forwardMostMove); // kludgeless board
7407                 } else {
7408                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7409                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7410                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7411                 }
7412           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7413             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7414                  gameMode == TwoMachinesPlay)
7415               SendToProgram("go\n", cps);
7416             return;
7417       } else
7418         if (gameMode == PlayFromGameFile) {
7419             /* Stop reading this game file */
7420             gameMode = EditGame;
7421             ModeHighlight();
7422         }
7423         currentMove = --forwardMostMove;
7424         DisplayMove(currentMove-1); /* before DisplayMoveError */
7425         SwitchClocks();
7426         DisplayBothClocks();
7427         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7428                 parseList[currentMove], cps->which);
7429         DisplayMoveError(buf1);
7430         DrawPosition(FALSE, boards[currentMove]);
7431
7432         /* [HGM] illegal-move claim should forfeit game when Xboard */
7433         /* only passes fully legal moves                            */
7434         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7435             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7436                                 "False illegal-move claim", GE_XBOARD );
7437         }
7438         return;
7439     }
7440     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7441         /* Program has a broken "time" command that
7442            outputs a string not ending in newline.
7443            Don't use it. */
7444         cps->sendTime = 0;
7445     }
7446
7447     /*
7448      * If chess program startup fails, exit with an error message.
7449      * Attempts to recover here are futile.
7450      */
7451     if ((StrStr(message, "unknown host") != NULL)
7452         || (StrStr(message, "No remote directory") != NULL)
7453         || (StrStr(message, "not found") != NULL)
7454         || (StrStr(message, "No such file") != NULL)
7455         || (StrStr(message, "can't alloc") != NULL)
7456         || (StrStr(message, "Permission denied") != NULL)) {
7457
7458         cps->maybeThinking = FALSE;
7459         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7460                 cps->which, cps->program, cps->host, message);
7461         RemoveInputSource(cps->isr);
7462         DisplayFatalError(buf1, 0, 1);
7463         return;
7464     }
7465
7466     /*
7467      * Look for hint output
7468      */
7469     if (sscanf(message, "Hint: %s", buf1) == 1) {
7470         if (cps == &first && hintRequested) {
7471             hintRequested = FALSE;
7472             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7473                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7474                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7475                                     PosFlags(forwardMostMove),
7476                                     fromY, fromX, toY, toX, promoChar, buf1);
7477                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7478                 DisplayInformation(buf2);
7479             } else {
7480                 /* Hint move could not be parsed!? */
7481               snprintf(buf2, sizeof(buf2),
7482                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7483                         buf1, cps->which);
7484                 DisplayError(buf2, 0);
7485             }
7486         } else {
7487             strcpy(lastHint, buf1);
7488         }
7489         return;
7490     }
7491
7492     /*
7493      * Ignore other messages if game is not in progress
7494      */
7495     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7496         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7497
7498     /*
7499      * look for win, lose, draw, or draw offer
7500      */
7501     if (strncmp(message, "1-0", 3) == 0) {
7502         char *p, *q, *r = "";
7503         p = strchr(message, '{');
7504         if (p) {
7505             q = strchr(p, '}');
7506             if (q) {
7507                 *q = NULLCHAR;
7508                 r = p + 1;
7509             }
7510         }
7511         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7512         return;
7513     } else if (strncmp(message, "0-1", 3) == 0) {
7514         char *p, *q, *r = "";
7515         p = strchr(message, '{');
7516         if (p) {
7517             q = strchr(p, '}');
7518             if (q) {
7519                 *q = NULLCHAR;
7520                 r = p + 1;
7521             }
7522         }
7523         /* Kludge for Arasan 4.1 bug */
7524         if (strcmp(r, "Black resigns") == 0) {
7525             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7526             return;
7527         }
7528         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7529         return;
7530     } else if (strncmp(message, "1/2", 3) == 0) {
7531         char *p, *q, *r = "";
7532         p = strchr(message, '{');
7533         if (p) {
7534             q = strchr(p, '}');
7535             if (q) {
7536                 *q = NULLCHAR;
7537                 r = p + 1;
7538             }
7539         }
7540
7541         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7542         return;
7543
7544     } else if (strncmp(message, "White resign", 12) == 0) {
7545         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7546         return;
7547     } else if (strncmp(message, "Black resign", 12) == 0) {
7548         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7549         return;
7550     } else if (strncmp(message, "White matches", 13) == 0 ||
7551                strncmp(message, "Black matches", 13) == 0   ) {
7552         /* [HGM] ignore GNUShogi noises */
7553         return;
7554     } else if (strncmp(message, "White", 5) == 0 &&
7555                message[5] != '(' &&
7556                StrStr(message, "Black") == NULL) {
7557         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7558         return;
7559     } else if (strncmp(message, "Black", 5) == 0 &&
7560                message[5] != '(') {
7561         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7562         return;
7563     } else if (strcmp(message, "resign") == 0 ||
7564                strcmp(message, "computer resigns") == 0) {
7565         switch (gameMode) {
7566           case MachinePlaysBlack:
7567           case IcsPlayingBlack:
7568             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7569             break;
7570           case MachinePlaysWhite:
7571           case IcsPlayingWhite:
7572             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7573             break;
7574           case TwoMachinesPlay:
7575             if (cps->twoMachinesColor[0] == 'w')
7576               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7577             else
7578               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7579             break;
7580           default:
7581             /* can't happen */
7582             break;
7583         }
7584         return;
7585     } else if (strncmp(message, "opponent mates", 14) == 0) {
7586         switch (gameMode) {
7587           case MachinePlaysBlack:
7588           case IcsPlayingBlack:
7589             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7590             break;
7591           case MachinePlaysWhite:
7592           case IcsPlayingWhite:
7593             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7594             break;
7595           case TwoMachinesPlay:
7596             if (cps->twoMachinesColor[0] == 'w')
7597               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7598             else
7599               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7600             break;
7601           default:
7602             /* can't happen */
7603             break;
7604         }
7605         return;
7606     } else if (strncmp(message, "computer mates", 14) == 0) {
7607         switch (gameMode) {
7608           case MachinePlaysBlack:
7609           case IcsPlayingBlack:
7610             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7611             break;
7612           case MachinePlaysWhite:
7613           case IcsPlayingWhite:
7614             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7615             break;
7616           case TwoMachinesPlay:
7617             if (cps->twoMachinesColor[0] == 'w')
7618               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7619             else
7620               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7621             break;
7622           default:
7623             /* can't happen */
7624             break;
7625         }
7626         return;
7627     } else if (strncmp(message, "checkmate", 9) == 0) {
7628         if (WhiteOnMove(forwardMostMove)) {
7629             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7630         } else {
7631             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7632         }
7633         return;
7634     } else if (strstr(message, "Draw") != NULL ||
7635                strstr(message, "game is a draw") != NULL) {
7636         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7637         return;
7638     } else if (strstr(message, "offer") != NULL &&
7639                strstr(message, "draw") != NULL) {
7640 #if ZIPPY
7641         if (appData.zippyPlay && first.initDone) {
7642             /* Relay offer to ICS */
7643             SendToICS(ics_prefix);
7644             SendToICS("draw\n");
7645         }
7646 #endif
7647         cps->offeredDraw = 2; /* valid until this engine moves twice */
7648         if (gameMode == TwoMachinesPlay) {
7649             if (cps->other->offeredDraw) {
7650                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7651             /* [HGM] in two-machine mode we delay relaying draw offer      */
7652             /* until after we also have move, to see if it is really claim */
7653             }
7654         } else if (gameMode == MachinePlaysWhite ||
7655                    gameMode == MachinePlaysBlack) {
7656           if (userOfferedDraw) {
7657             DisplayInformation(_("Machine accepts your draw offer"));
7658             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7659           } else {
7660             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7661           }
7662         }
7663     }
7664
7665
7666     /*
7667      * Look for thinking output
7668      */
7669     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7670           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7671                                 ) {
7672         int plylev, mvleft, mvtot, curscore, time;
7673         char mvname[MOVE_LEN];
7674         u64 nodes; // [DM]
7675         char plyext;
7676         int ignore = FALSE;
7677         int prefixHint = FALSE;
7678         mvname[0] = NULLCHAR;
7679
7680         switch (gameMode) {
7681           case MachinePlaysBlack:
7682           case IcsPlayingBlack:
7683             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7684             break;
7685           case MachinePlaysWhite:
7686           case IcsPlayingWhite:
7687             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7688             break;
7689           case AnalyzeMode:
7690           case AnalyzeFile:
7691             break;
7692           case IcsObserving: /* [DM] icsEngineAnalyze */
7693             if (!appData.icsEngineAnalyze) ignore = TRUE;
7694             break;
7695           case TwoMachinesPlay:
7696             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7697                 ignore = TRUE;
7698             }
7699             break;
7700           default:
7701             ignore = TRUE;
7702             break;
7703         }
7704
7705         if (!ignore) {
7706             buf1[0] = NULLCHAR;
7707             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7708                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7709
7710                 if (plyext != ' ' && plyext != '\t') {
7711                     time *= 100;
7712                 }
7713
7714                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7715                 if( cps->scoreIsAbsolute && 
7716                     ( gameMode == MachinePlaysBlack ||
7717                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7718                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7719                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7720                      !WhiteOnMove(currentMove)
7721                     ) )
7722                 {
7723                     curscore = -curscore;
7724                 }
7725
7726
7727                 programStats.depth = plylev;
7728                 programStats.nodes = nodes;
7729                 programStats.time = time;
7730                 programStats.score = curscore;
7731                 programStats.got_only_move = 0;
7732
7733                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7734                         int ticklen;
7735
7736                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7737                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7738                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7739                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7740                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7741                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7742                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7743                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7744                 }
7745
7746                 /* Buffer overflow protection */
7747                 if (buf1[0] != NULLCHAR) {
7748                     if (strlen(buf1) >= sizeof(programStats.movelist)
7749                         && appData.debugMode) {
7750                         fprintf(debugFP,
7751                                 "PV is too long; using the first %u bytes.\n",
7752                                 (unsigned) sizeof(programStats.movelist) - 1);
7753                     }
7754
7755                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7756                 } else {
7757                     sprintf(programStats.movelist, " no PV\n");
7758                 }
7759
7760                 if (programStats.seen_stat) {
7761                     programStats.ok_to_send = 1;
7762                 }
7763
7764                 if (strchr(programStats.movelist, '(') != NULL) {
7765                     programStats.line_is_book = 1;
7766                     programStats.nr_moves = 0;
7767                     programStats.moves_left = 0;
7768                 } else {
7769                     programStats.line_is_book = 0;
7770                 }
7771
7772                 SendProgramStatsToFrontend( cps, &programStats );
7773
7774                 /*
7775                     [AS] Protect the thinkOutput buffer from overflow... this
7776                     is only useful if buf1 hasn't overflowed first!
7777                 */
7778                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7779                         plylev,
7780                         (gameMode == TwoMachinesPlay ?
7781                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7782                         ((double) curscore) / 100.0,
7783                         prefixHint ? lastHint : "",
7784                         prefixHint ? " " : "" );
7785
7786                 if( buf1[0] != NULLCHAR ) {
7787                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7788
7789                     if( strlen(buf1) > max_len ) {
7790                         if( appData.debugMode) {
7791                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7792                         }
7793                         buf1[max_len+1] = '\0';
7794                     }
7795
7796                     strcat( thinkOutput, buf1 );
7797                 }
7798
7799                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7800                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7801                     DisplayMove(currentMove - 1);
7802                 }
7803                 return;
7804
7805             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7806                 /* crafty (9.25+) says "(only move) <move>"
7807                  * if there is only 1 legal move
7808                  */
7809                 sscanf(p, "(only move) %s", buf1);
7810                 sprintf(thinkOutput, "%s (only move)", buf1);
7811                 sprintf(programStats.movelist, "%s (only move)", buf1);
7812                 programStats.depth = 1;
7813                 programStats.nr_moves = 1;
7814                 programStats.moves_left = 1;
7815                 programStats.nodes = 1;
7816                 programStats.time = 1;
7817                 programStats.got_only_move = 1;
7818
7819                 /* Not really, but we also use this member to
7820                    mean "line isn't going to change" (Crafty
7821                    isn't searching, so stats won't change) */
7822                 programStats.line_is_book = 1;
7823
7824                 SendProgramStatsToFrontend( cps, &programStats );
7825
7826                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7827                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7828                     DisplayMove(currentMove - 1);
7829                 }
7830                 return;
7831             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7832                               &time, &nodes, &plylev, &mvleft,
7833                               &mvtot, mvname) >= 5) {
7834                 /* The stat01: line is from Crafty (9.29+) in response
7835                    to the "." command */
7836                 programStats.seen_stat = 1;
7837                 cps->maybeThinking = TRUE;
7838
7839                 if (programStats.got_only_move || !appData.periodicUpdates)
7840                   return;
7841
7842                 programStats.depth = plylev;
7843                 programStats.time = time;
7844                 programStats.nodes = nodes;
7845                 programStats.moves_left = mvleft;
7846                 programStats.nr_moves = mvtot;
7847                 strcpy(programStats.move_name, mvname);
7848                 programStats.ok_to_send = 1;
7849                 programStats.movelist[0] = '\0';
7850
7851                 SendProgramStatsToFrontend( cps, &programStats );
7852
7853                 return;
7854
7855             } else if (strncmp(message,"++",2) == 0) {
7856                 /* Crafty 9.29+ outputs this */
7857                 programStats.got_fail = 2;
7858                 return;
7859
7860             } else if (strncmp(message,"--",2) == 0) {
7861                 /* Crafty 9.29+ outputs this */
7862                 programStats.got_fail = 1;
7863                 return;
7864
7865             } else if (thinkOutput[0] != NULLCHAR &&
7866                        strncmp(message, "    ", 4) == 0) {
7867                 unsigned message_len;
7868
7869                 p = message;
7870                 while (*p && *p == ' ') p++;
7871
7872                 message_len = strlen( p );
7873
7874                 /* [AS] Avoid buffer overflow */
7875                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7876                     strcat(thinkOutput, " ");
7877                     strcat(thinkOutput, p);
7878                 }
7879
7880                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7881                     strcat(programStats.movelist, " ");
7882                     strcat(programStats.movelist, p);
7883                 }
7884
7885                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7886                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7887                     DisplayMove(currentMove - 1);
7888                 }
7889                 return;
7890             }
7891         }
7892         else {
7893             buf1[0] = NULLCHAR;
7894
7895             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7896                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7897             {
7898                 ChessProgramStats cpstats;
7899
7900                 if (plyext != ' ' && plyext != '\t') {
7901                     time *= 100;
7902                 }
7903
7904                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7905                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7906                     curscore = -curscore;
7907                 }
7908
7909                 cpstats.depth = plylev;
7910                 cpstats.nodes = nodes;
7911                 cpstats.time = time;
7912                 cpstats.score = curscore;
7913                 cpstats.got_only_move = 0;
7914                 cpstats.movelist[0] = '\0';
7915
7916                 if (buf1[0] != NULLCHAR) {
7917                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7918                 }
7919
7920                 cpstats.ok_to_send = 0;
7921                 cpstats.line_is_book = 0;
7922                 cpstats.nr_moves = 0;
7923                 cpstats.moves_left = 0;
7924
7925                 SendProgramStatsToFrontend( cps, &cpstats );
7926             }
7927         }
7928     }
7929 }
7930
7931
7932 /* Parse a game score from the character string "game", and
7933    record it as the history of the current game.  The game
7934    score is NOT assumed to start from the standard position.
7935    The display is not updated in any way.
7936    */
7937 void
7938 ParseGameHistory(game)
7939      char *game;
7940 {
7941     ChessMove moveType;
7942     int fromX, fromY, toX, toY, boardIndex;
7943     char promoChar;
7944     char *p, *q;
7945     char buf[MSG_SIZ];
7946
7947     if (appData.debugMode)
7948       fprintf(debugFP, "Parsing game history: %s\n", game);
7949
7950     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7951     gameInfo.site = StrSave(appData.icsHost);
7952     gameInfo.date = PGNDate();
7953     gameInfo.round = StrSave("-");
7954
7955     /* Parse out names of players */
7956     while (*game == ' ') game++;
7957     p = buf;
7958     while (*game != ' ') *p++ = *game++;
7959     *p = NULLCHAR;
7960     gameInfo.white = StrSave(buf);
7961     while (*game == ' ') game++;
7962     p = buf;
7963     while (*game != ' ' && *game != '\n') *p++ = *game++;
7964     *p = NULLCHAR;
7965     gameInfo.black = StrSave(buf);
7966
7967     /* Parse moves */
7968     boardIndex = blackPlaysFirst ? 1 : 0;
7969     yynewstr(game);
7970     for (;;) {
7971         yyboardindex = boardIndex;
7972         moveType = (ChessMove) yylex();
7973         switch (moveType) {
7974           case IllegalMove:             /* maybe suicide chess, etc. */
7975   if (appData.debugMode) {
7976     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7977     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7978     setbuf(debugFP, NULL);
7979   }
7980           case WhitePromotionChancellor:
7981           case BlackPromotionChancellor:
7982           case WhitePromotionArchbishop:
7983           case BlackPromotionArchbishop:
7984           case WhitePromotionQueen:
7985           case BlackPromotionQueen:
7986           case WhitePromotionRook:
7987           case BlackPromotionRook:
7988           case WhitePromotionBishop:
7989           case BlackPromotionBishop:
7990           case WhitePromotionKnight:
7991           case BlackPromotionKnight:
7992           case WhitePromotionKing:
7993           case BlackPromotionKing:
7994           case NormalMove:
7995           case WhiteCapturesEnPassant:
7996           case BlackCapturesEnPassant:
7997           case WhiteKingSideCastle:
7998           case WhiteQueenSideCastle:
7999           case BlackKingSideCastle:
8000           case BlackQueenSideCastle:
8001           case WhiteKingSideCastleWild:
8002           case WhiteQueenSideCastleWild:
8003           case BlackKingSideCastleWild:
8004           case BlackQueenSideCastleWild:
8005           /* PUSH Fabien */
8006           case WhiteHSideCastleFR:
8007           case WhiteASideCastleFR:
8008           case BlackHSideCastleFR:
8009           case BlackASideCastleFR:
8010           /* POP Fabien */
8011             fromX = currentMoveString[0] - AAA;
8012             fromY = currentMoveString[1] - ONE;
8013             toX = currentMoveString[2] - AAA;
8014             toY = currentMoveString[3] - ONE;
8015             promoChar = currentMoveString[4];
8016             break;
8017           case WhiteDrop:
8018           case BlackDrop:
8019             fromX = moveType == WhiteDrop ?
8020               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8021             (int) CharToPiece(ToLower(currentMoveString[0]));
8022             fromY = DROP_RANK;
8023             toX = currentMoveString[2] - AAA;
8024             toY = currentMoveString[3] - ONE;
8025             promoChar = NULLCHAR;
8026             break;
8027           case AmbiguousMove:
8028             /* bug? */
8029             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8030   if (appData.debugMode) {
8031     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8032     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8033     setbuf(debugFP, NULL);
8034   }
8035             DisplayError(buf, 0);
8036             return;
8037           case ImpossibleMove:
8038             /* bug? */
8039             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8040   if (appData.debugMode) {
8041     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8042     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8043     setbuf(debugFP, NULL);
8044   }
8045             DisplayError(buf, 0);
8046             return;
8047           case (ChessMove) 0:   /* end of file */
8048             if (boardIndex < backwardMostMove) {
8049                 /* Oops, gap.  How did that happen? */
8050                 DisplayError(_("Gap in move list"), 0);
8051                 return;
8052             }
8053             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8054             if (boardIndex > forwardMostMove) {
8055                 forwardMostMove = boardIndex;
8056             }
8057             return;
8058           case ElapsedTime:
8059             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8060                 strcat(parseList[boardIndex-1], " ");
8061                 strcat(parseList[boardIndex-1], yy_text);
8062             }
8063             continue;
8064           case Comment:
8065           case PGNTag:
8066           case NAG:
8067           default:
8068             /* ignore */
8069             continue;
8070           case WhiteWins:
8071           case BlackWins:
8072           case GameIsDrawn:
8073           case GameUnfinished:
8074             if (gameMode == IcsExamining) {
8075                 if (boardIndex < backwardMostMove) {
8076                     /* Oops, gap.  How did that happen? */
8077                     return;
8078                 }
8079                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8080                 return;
8081             }
8082             gameInfo.result = moveType;
8083             p = strchr(yy_text, '{');
8084             if (p == NULL) p = strchr(yy_text, '(');
8085             if (p == NULL) {
8086                 p = yy_text;
8087                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8088             } else {
8089                 q = strchr(p, *p == '{' ? '}' : ')');
8090                 if (q != NULL) *q = NULLCHAR;
8091                 p++;
8092             }
8093             gameInfo.resultDetails = StrSave(p);
8094             continue;
8095         }
8096         if (boardIndex >= forwardMostMove &&
8097             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8098             backwardMostMove = blackPlaysFirst ? 1 : 0;
8099             return;
8100         }
8101         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8102                                  fromY, fromX, toY, toX, promoChar,
8103                                  parseList[boardIndex]);
8104         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8105         /* currentMoveString is set as a side-effect of yylex */
8106         strcpy(moveList[boardIndex], currentMoveString);
8107         strcat(moveList[boardIndex], "\n");
8108         boardIndex++;
8109         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8110         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8111           case MT_NONE:
8112           case MT_STALEMATE:
8113           default:
8114             break;
8115           case MT_CHECK:
8116             if(gameInfo.variant != VariantShogi)
8117                 strcat(parseList[boardIndex - 1], "+");
8118             break;
8119           case MT_CHECKMATE:
8120           case MT_STAINMATE:
8121             strcat(parseList[boardIndex - 1], "#");
8122             break;
8123         }
8124     }
8125 }
8126
8127
8128 /* Apply a move to the given board  */
8129 void
8130 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8131      int fromX, fromY, toX, toY;
8132      int promoChar;
8133      Board board;
8134 {
8135   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8136   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8137
8138     /* [HGM] compute & store e.p. status and castling rights for new position */
8139     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8140     { int i;
8141
8142       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8143       oldEP = (signed char)board[EP_STATUS];
8144       board[EP_STATUS] = EP_NONE;
8145
8146       if( board[toY][toX] != EmptySquare ) 
8147            board[EP_STATUS] = EP_CAPTURE;  
8148
8149       if( board[fromY][fromX] == WhitePawn ) {
8150            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8151                board[EP_STATUS] = EP_PAWN_MOVE;
8152            if( toY-fromY==2) {
8153                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8154                         gameInfo.variant != VariantBerolina || toX < fromX)
8155                       board[EP_STATUS] = toX | berolina;
8156                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8157                   gameInfo.variant != VariantBerolina || toX > fromX) 
8158                  board[EP_STATUS] = toX;
8159            }
8160       } else
8161       if( board[fromY][fromX] == BlackPawn ) {
8162            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8163                board[EP_STATUS] = EP_PAWN_MOVE; 
8164            if( toY-fromY== -2) {
8165                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8166                         gameInfo.variant != VariantBerolina || toX < fromX)
8167                       board[EP_STATUS] = toX | berolina;
8168                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8169                         gameInfo.variant != VariantBerolina || toX > fromX) 
8170                       board[EP_STATUS] = toX;
8171            }
8172        }
8173
8174        for(i=0; i<nrCastlingRights; i++) {
8175            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8176               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8177              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8178        }
8179
8180     }
8181
8182   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8183   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8184        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8185
8186   if (fromX == toX && fromY == toY) return;
8187
8188   if (fromY == DROP_RANK) {
8189         /* must be first */
8190         piece = board[toY][toX] = (ChessSquare) fromX;
8191   } else {
8192      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8193      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8194      if(gameInfo.variant == VariantKnightmate)
8195          king += (int) WhiteUnicorn - (int) WhiteKing;
8196
8197     /* Code added by Tord: */
8198     /* FRC castling assumed when king captures friendly rook. */
8199     if (board[fromY][fromX] == WhiteKing &&
8200              board[toY][toX] == WhiteRook) {
8201       board[fromY][fromX] = EmptySquare;
8202       board[toY][toX] = EmptySquare;
8203       if(toX > fromX) {
8204         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8205       } else {
8206         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8207       }
8208     } else if (board[fromY][fromX] == BlackKing &&
8209                board[toY][toX] == BlackRook) {
8210       board[fromY][fromX] = EmptySquare;
8211       board[toY][toX] = EmptySquare;
8212       if(toX > fromX) {
8213         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8214       } else {
8215         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8216       }
8217     /* End of code added by Tord */
8218
8219     } else if (board[fromY][fromX] == king
8220         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8221         && toY == fromY && toX > fromX+1) {
8222         board[fromY][fromX] = EmptySquare;
8223         board[toY][toX] = king;
8224         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8225         board[fromY][BOARD_RGHT-1] = EmptySquare;
8226     } else if (board[fromY][fromX] == king
8227         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8228                && toY == fromY && toX < fromX-1) {
8229         board[fromY][fromX] = EmptySquare;
8230         board[toY][toX] = king;
8231         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8232         board[fromY][BOARD_LEFT] = EmptySquare;
8233     } else if (board[fromY][fromX] == WhitePawn
8234                && toY >= BOARD_HEIGHT-promoRank
8235                && gameInfo.variant != VariantXiangqi
8236                ) {
8237         /* white pawn promotion */
8238         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8239         if (board[toY][toX] == EmptySquare) {
8240             board[toY][toX] = WhiteQueen;
8241         }
8242         if(gameInfo.variant==VariantBughouse ||
8243            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8244             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8245         board[fromY][fromX] = EmptySquare;
8246     } else if ((fromY == BOARD_HEIGHT-4)
8247                && (toX != fromX)
8248                && gameInfo.variant != VariantXiangqi
8249                && gameInfo.variant != VariantBerolina
8250                && (board[fromY][fromX] == WhitePawn)
8251                && (board[toY][toX] == EmptySquare)) {
8252         board[fromY][fromX] = EmptySquare;
8253         board[toY][toX] = WhitePawn;
8254         captured = board[toY - 1][toX];
8255         board[toY - 1][toX] = EmptySquare;
8256     } else if ((fromY == BOARD_HEIGHT-4)
8257                && (toX == fromX)
8258                && gameInfo.variant == VariantBerolina
8259                && (board[fromY][fromX] == WhitePawn)
8260                && (board[toY][toX] == EmptySquare)) {
8261         board[fromY][fromX] = EmptySquare;
8262         board[toY][toX] = WhitePawn;
8263         if(oldEP & EP_BEROLIN_A) {
8264                 captured = board[fromY][fromX-1];
8265                 board[fromY][fromX-1] = EmptySquare;
8266         }else{  captured = board[fromY][fromX+1];
8267                 board[fromY][fromX+1] = EmptySquare;
8268         }
8269     } else if (board[fromY][fromX] == king
8270         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8271                && toY == fromY && toX > fromX+1) {
8272         board[fromY][fromX] = EmptySquare;
8273         board[toY][toX] = king;
8274         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8275         board[fromY][BOARD_RGHT-1] = EmptySquare;
8276     } else if (board[fromY][fromX] == king
8277         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8278                && toY == fromY && toX < fromX-1) {
8279         board[fromY][fromX] = EmptySquare;
8280         board[toY][toX] = king;
8281         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8282         board[fromY][BOARD_LEFT] = EmptySquare;
8283     } else if (fromY == 7 && fromX == 3
8284                && board[fromY][fromX] == BlackKing
8285                && toY == 7 && toX == 5) {
8286         board[fromY][fromX] = EmptySquare;
8287         board[toY][toX] = BlackKing;
8288         board[fromY][7] = EmptySquare;
8289         board[toY][4] = BlackRook;
8290     } else if (fromY == 7 && fromX == 3
8291                && board[fromY][fromX] == BlackKing
8292                && toY == 7 && toX == 1) {
8293         board[fromY][fromX] = EmptySquare;
8294         board[toY][toX] = BlackKing;
8295         board[fromY][0] = EmptySquare;
8296         board[toY][2] = BlackRook;
8297     } else if (board[fromY][fromX] == BlackPawn
8298                && toY < promoRank
8299                && gameInfo.variant != VariantXiangqi
8300                ) {
8301         /* black pawn promotion */
8302         board[toY][toX] = CharToPiece(ToLower(promoChar));
8303         if (board[toY][toX] == EmptySquare) {
8304             board[toY][toX] = BlackQueen;
8305         }
8306         if(gameInfo.variant==VariantBughouse ||
8307            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8308             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8309         board[fromY][fromX] = EmptySquare;
8310     } else if ((fromY == 3)
8311                && (toX != fromX)
8312                && gameInfo.variant != VariantXiangqi
8313                && gameInfo.variant != VariantBerolina
8314                && (board[fromY][fromX] == BlackPawn)
8315                && (board[toY][toX] == EmptySquare)) {
8316         board[fromY][fromX] = EmptySquare;
8317         board[toY][toX] = BlackPawn;
8318         captured = board[toY + 1][toX];
8319         board[toY + 1][toX] = EmptySquare;
8320     } else if ((fromY == 3)
8321                && (toX == fromX)
8322                && gameInfo.variant == VariantBerolina
8323                && (board[fromY][fromX] == BlackPawn)
8324                && (board[toY][toX] == EmptySquare)) {
8325         board[fromY][fromX] = EmptySquare;
8326         board[toY][toX] = BlackPawn;
8327         if(oldEP & EP_BEROLIN_A) {
8328                 captured = board[fromY][fromX-1];
8329                 board[fromY][fromX-1] = EmptySquare;
8330         }else{  captured = board[fromY][fromX+1];
8331                 board[fromY][fromX+1] = EmptySquare;
8332         }
8333     } else {
8334         board[toY][toX] = board[fromY][fromX];
8335         board[fromY][fromX] = EmptySquare;
8336     }
8337
8338     /* [HGM] now we promote for Shogi, if needed */
8339     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8340         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8341   }
8342
8343     if (gameInfo.holdingsWidth != 0) {
8344
8345       /* !!A lot more code needs to be written to support holdings  */
8346       /* [HGM] OK, so I have written it. Holdings are stored in the */
8347       /* penultimate board files, so they are automaticlly stored   */
8348       /* in the game history.                                       */
8349       if (fromY == DROP_RANK) {
8350         /* Delete from holdings, by decreasing count */
8351         /* and erasing image if necessary            */
8352         p = (int) fromX;
8353         if(p < (int) BlackPawn) { /* white drop */
8354              p -= (int)WhitePawn;
8355                  p = PieceToNumber((ChessSquare)p);
8356              if(p >= gameInfo.holdingsSize) p = 0;
8357              if(--board[p][BOARD_WIDTH-2] <= 0)
8358                   board[p][BOARD_WIDTH-1] = EmptySquare;
8359              if((int)board[p][BOARD_WIDTH-2] < 0)
8360                         board[p][BOARD_WIDTH-2] = 0;
8361         } else {                  /* black drop */
8362              p -= (int)BlackPawn;
8363                  p = PieceToNumber((ChessSquare)p);
8364              if(p >= gameInfo.holdingsSize) p = 0;
8365              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8366                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8367              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8368                         board[BOARD_HEIGHT-1-p][1] = 0;
8369         }
8370       }
8371       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8372           && gameInfo.variant != VariantBughouse        ) {
8373         /* [HGM] holdings: Add to holdings, if holdings exist */
8374         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8375                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8376                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8377         }
8378         p = (int) captured;
8379         if (p >= (int) BlackPawn) {
8380           p -= (int)BlackPawn;
8381           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8382                   /* in Shogi restore piece to its original  first */
8383                   captured = (ChessSquare) (DEMOTED captured);
8384                   p = DEMOTED p;
8385           }
8386           p = PieceToNumber((ChessSquare)p);
8387           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8388           board[p][BOARD_WIDTH-2]++;
8389           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8390         } else {
8391           p -= (int)WhitePawn;
8392           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8393                   captured = (ChessSquare) (DEMOTED captured);
8394                   p = DEMOTED p;
8395           }
8396           p = PieceToNumber((ChessSquare)p);
8397           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8398           board[BOARD_HEIGHT-1-p][1]++;
8399           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8400         }
8401       }
8402     } else if (gameInfo.variant == VariantAtomic) {
8403       if (captured != EmptySquare) {
8404         int y, x;
8405         for (y = toY-1; y <= toY+1; y++) {
8406           for (x = toX-1; x <= toX+1; x++) {
8407             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8408                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8409               board[y][x] = EmptySquare;
8410             }
8411           }
8412         }
8413         board[toY][toX] = EmptySquare;
8414       }
8415     }
8416     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8417         /* [HGM] Shogi promotions */
8418         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8419     }
8420
8421     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8422                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8423         // [HGM] superchess: take promotion piece out of holdings
8424         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8425         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8426             if(!--board[k][BOARD_WIDTH-2])
8427                 board[k][BOARD_WIDTH-1] = EmptySquare;
8428         } else {
8429             if(!--board[BOARD_HEIGHT-1-k][1])
8430                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8431         }
8432     }
8433
8434 }
8435
8436 /* Updates forwardMostMove */
8437 void
8438 MakeMove(fromX, fromY, toX, toY, promoChar)
8439      int fromX, fromY, toX, toY;
8440      int promoChar;
8441 {
8442 //    forwardMostMove++; // [HGM] bare: moved downstream
8443
8444     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8445         int timeLeft; static int lastLoadFlag=0; int king, piece;
8446         piece = boards[forwardMostMove][fromY][fromX];
8447         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8448         if(gameInfo.variant == VariantKnightmate)
8449             king += (int) WhiteUnicorn - (int) WhiteKing;
8450         if(forwardMostMove == 0) {
8451             if(blackPlaysFirst)
8452                 fprintf(serverMoves, "%s;", second.tidy);
8453             fprintf(serverMoves, "%s;", first.tidy);
8454             if(!blackPlaysFirst)
8455                 fprintf(serverMoves, "%s;", second.tidy);
8456         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8457         lastLoadFlag = loadFlag;
8458         // print base move
8459         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8460         // print castling suffix
8461         if( toY == fromY && piece == king ) {
8462             if(toX-fromX > 1)
8463                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8464             if(fromX-toX >1)
8465                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8466         }
8467         // e.p. suffix
8468         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8469              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8470              boards[forwardMostMove][toY][toX] == EmptySquare
8471              && fromX != toX )
8472                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8473         // promotion suffix
8474         if(promoChar != NULLCHAR)
8475                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8476         if(!loadFlag) {
8477             fprintf(serverMoves, "/%d/%d",
8478                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8479             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8480             else                      timeLeft = blackTimeRemaining/1000;
8481             fprintf(serverMoves, "/%d", timeLeft);
8482         }
8483         fflush(serverMoves);
8484     }
8485
8486     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8487       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8488                         0, 1);
8489       return;
8490     }
8491     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8492     if (commentList[forwardMostMove+1] != NULL) {
8493         free(commentList[forwardMostMove+1]);
8494         commentList[forwardMostMove+1] = NULL;
8495     }
8496     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8497     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8498     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8499     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8500     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8501     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8502     gameInfo.result = GameUnfinished;
8503     if (gameInfo.resultDetails != NULL) {
8504         free(gameInfo.resultDetails);
8505         gameInfo.resultDetails = NULL;
8506     }
8507     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8508                               moveList[forwardMostMove - 1]);
8509     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8510                              PosFlags(forwardMostMove - 1),
8511                              fromY, fromX, toY, toX, promoChar,
8512                              parseList[forwardMostMove - 1]);
8513     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8514       case MT_NONE:
8515       case MT_STALEMATE:
8516       default:
8517         break;
8518       case MT_CHECK:
8519         if(gameInfo.variant != VariantShogi)
8520             strcat(parseList[forwardMostMove - 1], "+");
8521         break;
8522       case MT_CHECKMATE:
8523       case MT_STAINMATE:
8524         strcat(parseList[forwardMostMove - 1], "#");
8525         break;
8526     }
8527     if (appData.debugMode) {
8528         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8529     }
8530
8531 }
8532
8533 /* Updates currentMove if not pausing */
8534 void
8535 ShowMove(fromX, fromY, toX, toY)
8536 {
8537     int instant = (gameMode == PlayFromGameFile) ?
8538         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8539
8540     if(appData.noGUI) return;
8541
8542     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
8543       {
8544         if (!instant)
8545           {
8546             if (forwardMostMove == currentMove + 1)
8547               {
8548 //TODO
8549 //              AnimateMove(boards[forwardMostMove - 1],
8550 //                          fromX, fromY, toX, toY);
8551               }
8552             if (appData.highlightLastMove)
8553               {
8554                 SetHighlights(fromX, fromY, toX, toY);
8555               }
8556           }
8557         currentMove = forwardMostMove;
8558     }
8559
8560     if (instant) return;
8561
8562     DisplayMove(currentMove - 1);
8563     DrawPosition(FALSE, boards[currentMove]);
8564     DisplayBothClocks();
8565     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8566
8567     return;
8568 }
8569
8570 void SendEgtPath(ChessProgramState *cps)
8571 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8572         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8573
8574         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8575
8576         while(*p) {
8577             char c, *q = name+1, *r, *s;
8578
8579             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8580             while(*p && *p != ',') *q++ = *p++;
8581             *q++ = ':'; *q = 0;
8582             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8583                 strcmp(name, ",nalimov:") == 0 ) {
8584                 // take nalimov path from the menu-changeable option first, if it is defined
8585                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8586                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8587             } else
8588             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8589                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8590                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8591                 s = r = StrStr(s, ":") + 1; // beginning of path info
8592                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8593                 c = *r; *r = 0;             // temporarily null-terminate path info
8594                     *--q = 0;               // strip of trailig ':' from name
8595                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8596                 *r = c;
8597                 SendToProgram(buf,cps);     // send egtbpath command for this format
8598             }
8599             if(*p == ',') p++; // read away comma to position for next format name
8600         }
8601 }
8602
8603 void
8604 InitChessProgram(cps, setup)
8605      ChessProgramState *cps;
8606      int setup; /* [HGM] needed to setup FRC opening position */
8607 {
8608     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8609     if (appData.noChessProgram) return;
8610     hintRequested = FALSE;
8611     bookRequested = FALSE;
8612
8613     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8614     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8615     if(cps->memSize) { /* [HGM] memory */
8616         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8617         SendToProgram(buf, cps);
8618     }
8619     SendEgtPath(cps); /* [HGM] EGT */
8620     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8621         sprintf(buf, "cores %d\n", appData.smpCores);
8622         SendToProgram(buf, cps);
8623     }
8624
8625     SendToProgram(cps->initString, cps);
8626     if (gameInfo.variant != VariantNormal &&
8627         gameInfo.variant != VariantLoadable
8628         /* [HGM] also send variant if board size non-standard */
8629         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8630                                             ) {
8631       char *v = VariantName(gameInfo.variant);
8632       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8633         /* [HGM] in protocol 1 we have to assume all variants valid */
8634         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8635         DisplayFatalError(buf, 0, 1);
8636         return;
8637       }
8638
8639       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8640       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8641       if( gameInfo.variant == VariantXiangqi )
8642            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8643       if( gameInfo.variant == VariantShogi )
8644            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8645       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8646            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8647       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8648                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8649            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8650       if( gameInfo.variant == VariantCourier )
8651            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8652       if( gameInfo.variant == VariantSuper )
8653            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8654       if( gameInfo.variant == VariantGreat )
8655            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8656
8657       if(overruled) {
8658            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8659                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8660            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8661            if(StrStr(cps->variants, b) == NULL) {
8662                // specific sized variant not known, check if general sizing allowed
8663                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8664                    if(StrStr(cps->variants, "boardsize") == NULL) {
8665                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8666                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8667                        DisplayFatalError(buf, 0, 1);
8668                        return;
8669                    }
8670                    /* [HGM] here we really should compare with the maximum supported board size */
8671                }
8672            }
8673       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8674       sprintf(buf, "variant %s\n", b);
8675       SendToProgram(buf, cps);
8676     }
8677     currentlyInitializedVariant = gameInfo.variant;
8678
8679     /* [HGM] send opening position in FRC to first engine */
8680     if(setup) {
8681           SendToProgram("force\n", cps);
8682           SendBoard(cps, 0);
8683           /* engine is now in force mode! Set flag to wake it up after first move. */
8684           setboardSpoiledMachineBlack = 1;
8685     }
8686
8687     if (cps->sendICS) {
8688       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8689       SendToProgram(buf, cps);
8690     }
8691     cps->maybeThinking = FALSE;
8692     cps->offeredDraw = 0;
8693     if (!appData.icsActive) {
8694         SendTimeControl(cps, movesPerSession, timeControl,
8695                         timeIncrement, appData.searchDepth,
8696                         searchTime);
8697     }
8698     if (appData.showThinking
8699         // [HGM] thinking: four options require thinking output to be sent
8700         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8701                                 ) {
8702         SendToProgram("post\n", cps);
8703     }
8704     SendToProgram("hard\n", cps);
8705     if (!appData.ponderNextMove) {
8706         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8707            it without being sure what state we are in first.  "hard"
8708            is not a toggle, so that one is OK.
8709          */
8710         SendToProgram("easy\n", cps);
8711     }
8712     if (cps->usePing) {
8713       sprintf(buf, "ping %d\n", ++cps->lastPing);
8714       SendToProgram(buf, cps);
8715     }
8716     cps->initDone = TRUE;
8717 }
8718
8719
8720 void
8721 StartChessProgram(cps)
8722      ChessProgramState *cps;
8723 {
8724     char buf[MSG_SIZ];
8725     int err;
8726
8727     if (appData.noChessProgram) return;
8728     cps->initDone = FALSE;
8729
8730     if (strcmp(cps->host, "localhost") == 0) {
8731         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8732     } else if (*appData.remoteShell == NULLCHAR) {
8733         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8734     } else {
8735         if (*appData.remoteUser == NULLCHAR) {
8736           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8737                     cps->program);
8738         } else {
8739           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8740                     cps->host, appData.remoteUser, cps->program);
8741         }
8742         err = StartChildProcess(buf, "", &cps->pr);
8743     }
8744
8745     if (err != 0) {
8746         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8747         DisplayFatalError(buf, err, 1);
8748         cps->pr = NoProc;
8749         cps->isr = NULL;
8750         return;
8751     }
8752
8753     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8754     if (cps->protocolVersion > 1) {
8755       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8756       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8757       cps->comboCnt = 0;  //                and values of combo boxes
8758       SendToProgram(buf, cps);
8759     } else {
8760       SendToProgram("xboard\n", cps);
8761     }
8762 }
8763
8764
8765 void
8766 TwoMachinesEventIfReady P((void))
8767 {
8768   if (first.lastPing != first.lastPong) {
8769     DisplayMessage("", _("Waiting for first chess program"));
8770     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8771     return;
8772   }
8773   if (second.lastPing != second.lastPong) {
8774     DisplayMessage("", _("Waiting for second chess program"));
8775     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8776     return;
8777   }
8778   ThawUI();
8779   TwoMachinesEvent();
8780 }
8781
8782 void
8783 NextMatchGame P((void))
8784 {
8785     int index; /* [HGM] autoinc: step load index during match */
8786     Reset(FALSE, TRUE);
8787     if (*appData.loadGameFile != NULLCHAR) {
8788         index = appData.loadGameIndex;
8789         if(index < 0) { // [HGM] autoinc
8790             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8791             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8792         }
8793         LoadGameFromFile(appData.loadGameFile,
8794                          index,
8795                          appData.loadGameFile, FALSE);
8796     } else if (*appData.loadPositionFile != NULLCHAR) {
8797         index = appData.loadPositionIndex;
8798         if(index < 0) { // [HGM] autoinc
8799             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8800             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8801         }
8802         LoadPositionFromFile(appData.loadPositionFile,
8803                              index,
8804                              appData.loadPositionFile);
8805     }
8806     TwoMachinesEventIfReady();
8807 }
8808
8809 void UserAdjudicationEvent( int result )
8810 {
8811     ChessMove gameResult = GameIsDrawn;
8812
8813     if( result > 0 ) {
8814         gameResult = WhiteWins;
8815     }
8816     else if( result < 0 ) {
8817         gameResult = BlackWins;
8818     }
8819
8820     if( gameMode == TwoMachinesPlay ) {
8821         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8822     }
8823 }
8824
8825
8826 // [HGM] save: calculate checksum of game to make games easily identifiable
8827 int StringCheckSum(char *s)
8828 {
8829         int i = 0;
8830         if(s==NULL) return 0;
8831         while(*s) i = i*259 + *s++;
8832         return i;
8833 }
8834
8835 int GameCheckSum()
8836 {
8837         int i, sum=0;
8838         for(i=backwardMostMove; i<forwardMostMove; i++) {
8839                 sum += pvInfoList[i].depth;
8840                 sum += StringCheckSum(parseList[i]);
8841                 sum += StringCheckSum(commentList[i]);
8842                 sum *= 261;
8843         }
8844         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8845         return sum + StringCheckSum(commentList[i]);
8846 } // end of save patch
8847
8848 void
8849 GameEnds(result, resultDetails, whosays)
8850      ChessMove result;
8851      char *resultDetails;
8852      int whosays;
8853 {
8854     GameMode nextGameMode;
8855     int isIcsGame;
8856     char buf[MSG_SIZ];
8857
8858     if(endingGame) return; /* [HGM] crash: forbid recursion */
8859     endingGame = 1;
8860
8861     if (appData.debugMode) {
8862       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8863               result, resultDetails ? resultDetails : "(null)", whosays);
8864     }
8865
8866     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8867         /* If we are playing on ICS, the server decides when the
8868            game is over, but the engine can offer to draw, claim
8869            a draw, or resign.
8870          */
8871 #if ZIPPY
8872         if (appData.zippyPlay && first.initDone) {
8873             if (result == GameIsDrawn) {
8874                 /* In case draw still needs to be claimed */
8875                 SendToICS(ics_prefix);
8876                 SendToICS("draw\n");
8877             } else if (StrCaseStr(resultDetails, "resign")) {
8878                 SendToICS(ics_prefix);
8879                 SendToICS("resign\n");
8880             }
8881         }
8882 #endif
8883         endingGame = 0; /* [HGM] crash */
8884         return;
8885     }
8886
8887     /* If we're loading the game from a file, stop */
8888     if (whosays == GE_FILE) {
8889       (void) StopLoadGameTimer();
8890       gameFileFP = NULL;
8891     }
8892
8893     /* Cancel draw offers */
8894     first.offeredDraw = second.offeredDraw = 0;
8895
8896     /* If this is an ICS game, only ICS can really say it's done;
8897        if not, anyone can. */
8898     isIcsGame = (gameMode == IcsPlayingWhite ||
8899                  gameMode == IcsPlayingBlack ||
8900                  gameMode == IcsObserving    ||
8901                  gameMode == IcsExamining);
8902
8903     if (!isIcsGame || whosays == GE_ICS) {
8904         /* OK -- not an ICS game, or ICS said it was done */
8905         StopClocks();
8906         if (!isIcsGame && !appData.noChessProgram)
8907           SetUserThinkingEnables();
8908
8909         /* [HGM] if a machine claims the game end we verify this claim */
8910         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8911             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8912                 char claimer;
8913                 ChessMove trueResult = (ChessMove) -1;
8914
8915                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8916                                             first.twoMachinesColor[0] :
8917                                             second.twoMachinesColor[0] ;
8918
8919                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8920                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8921                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8922                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8923                 } else
8924                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8925                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8926                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8927                 } else
8928                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8929                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8930                 }
8931
8932                 // now verify win claims, but not in drop games, as we don't understand those yet
8933                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8934                                                  || gameInfo.variant == VariantGreat) &&
8935                     (result == WhiteWins && claimer == 'w' ||
8936                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8937                       if (appData.debugMode) {
8938                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8939                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8940                       }
8941                       if(result != trueResult) {
8942                               sprintf(buf, "False win claim: '%s'", resultDetails);
8943                               result = claimer == 'w' ? BlackWins : WhiteWins;
8944                               resultDetails = buf;
8945                       }
8946                 } else
8947                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8948                     && (forwardMostMove <= backwardMostMove ||
8949                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8950                         (claimer=='b')==(forwardMostMove&1))
8951                                                                                   ) {
8952                       /* [HGM] verify: draws that were not flagged are false claims */
8953                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8954                       result = claimer == 'w' ? BlackWins : WhiteWins;
8955                       resultDetails = buf;
8956                 }
8957                 /* (Claiming a loss is accepted no questions asked!) */
8958             }
8959
8960             /* [HGM] bare: don't allow bare King to win */
8961             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8962                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8963                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8964                && result != GameIsDrawn)
8965             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8966                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8967                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8968                         if(p >= 0 && p <= (int)WhiteKing) k++;
8969                 }
8970                 if (appData.debugMode) {
8971                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8972                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8973                 }
8974                 if(k <= 1) {
8975                         result = GameIsDrawn;
8976                         sprintf(buf, "%s but bare king", resultDetails);
8977                         resultDetails = buf;
8978                 }
8979             }
8980         }
8981
8982         if(serverMoves != NULL && !loadFlag) { char c = '=';
8983             if(result==WhiteWins) c = '+';
8984             if(result==BlackWins) c = '-';
8985             if(resultDetails != NULL)
8986                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8987         }
8988         if (resultDetails != NULL) {
8989             gameInfo.result = result;
8990             gameInfo.resultDetails = StrSave(resultDetails);
8991
8992             /* display last move only if game was not loaded from file */
8993             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8994                 DisplayMove(currentMove - 1);
8995
8996             if (forwardMostMove != 0) {
8997                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8998                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8999                                                                 ) {
9000                     if (*appData.saveGameFile != NULLCHAR) {
9001                         SaveGameToFile(appData.saveGameFile, TRUE);
9002                     } else if (appData.autoSaveGames) {
9003                         AutoSaveGame();
9004                     }
9005                     if (*appData.savePositionFile != NULLCHAR) {
9006                         SavePositionToFile(appData.savePositionFile);
9007                     }
9008                 }
9009             }
9010
9011             /* Tell program how game ended in case it is learning */
9012             /* [HGM] Moved this to after saving the PGN, just in case */
9013             /* engine died and we got here through time loss. In that */
9014             /* case we will get a fatal error writing the pipe, which */
9015             /* would otherwise lose us the PGN.                       */
9016             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9017             /* output during GameEnds should never be fatal anymore   */
9018             if (gameMode == MachinePlaysWhite ||
9019                 gameMode == MachinePlaysBlack ||
9020                 gameMode == TwoMachinesPlay ||
9021                 gameMode == IcsPlayingWhite ||
9022                 gameMode == IcsPlayingBlack ||
9023                 gameMode == BeginningOfGame) {
9024                 char buf[MSG_SIZ];
9025                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9026                         resultDetails);
9027                 if (first.pr != NoProc) {
9028                     SendToProgram(buf, &first);
9029                 }
9030                 if (second.pr != NoProc &&
9031                     gameMode == TwoMachinesPlay) {
9032                     SendToProgram(buf, &second);
9033                 }
9034             }
9035         }
9036
9037         if (appData.icsActive) {
9038             if (appData.quietPlay &&
9039                 (gameMode == IcsPlayingWhite ||
9040                  gameMode == IcsPlayingBlack)) {
9041                 SendToICS(ics_prefix);
9042                 SendToICS("set shout 1\n");
9043             }
9044             nextGameMode = IcsIdle;
9045             ics_user_moved = FALSE;
9046             /* clean up premove.  It's ugly when the game has ended and the
9047              * premove highlights are still on the board.
9048              */
9049             if (gotPremove) {
9050               gotPremove = FALSE;
9051               ClearPremoveHighlights();
9052               DrawPosition(FALSE, boards[currentMove]);
9053             }
9054             if (whosays == GE_ICS) {
9055                 switch (result) {
9056                 case WhiteWins:
9057                     if (gameMode == IcsPlayingWhite)
9058                         PlayIcsWinSound();
9059                     else if(gameMode == IcsPlayingBlack)
9060                         PlayIcsLossSound();
9061                     break;
9062                 case BlackWins:
9063                     if (gameMode == IcsPlayingBlack)
9064                         PlayIcsWinSound();
9065                     else if(gameMode == IcsPlayingWhite)
9066                         PlayIcsLossSound();
9067                     break;
9068                 case GameIsDrawn:
9069                     PlayIcsDrawSound();
9070                     break;
9071                 default:
9072                     PlayIcsUnfinishedSound();
9073                 }
9074             }
9075         } else if (gameMode == EditGame ||
9076                    gameMode == PlayFromGameFile ||
9077                    gameMode == AnalyzeMode ||
9078                    gameMode == AnalyzeFile) {
9079             nextGameMode = gameMode;
9080         } else {
9081             nextGameMode = EndOfGame;
9082         }
9083         pausing = FALSE;
9084         ModeHighlight();
9085     } else {
9086         nextGameMode = gameMode;
9087     }
9088
9089     if (appData.noChessProgram) {
9090         gameMode = nextGameMode;
9091         ModeHighlight();
9092         endingGame = 0; /* [HGM] crash */
9093         return;
9094     }
9095
9096     if (first.reuse) {
9097         /* Put first chess program into idle state */
9098         if (first.pr != NoProc &&
9099             (gameMode == MachinePlaysWhite ||
9100              gameMode == MachinePlaysBlack ||
9101              gameMode == TwoMachinesPlay ||
9102              gameMode == IcsPlayingWhite ||
9103              gameMode == IcsPlayingBlack ||
9104              gameMode == BeginningOfGame)) {
9105             SendToProgram("force\n", &first);
9106             if (first.usePing) {
9107               char buf[MSG_SIZ];
9108               sprintf(buf, "ping %d\n", ++first.lastPing);
9109               SendToProgram(buf, &first);
9110             }
9111         }
9112     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9113         /* Kill off first chess program */
9114         if (first.isr != NULL)
9115           RemoveInputSource(first.isr);
9116         first.isr = NULL;
9117
9118         if (first.pr != NoProc) {
9119             ExitAnalyzeMode();
9120             DoSleep( appData.delayBeforeQuit );
9121             SendToProgram("quit\n", &first);
9122             DoSleep( appData.delayAfterQuit );
9123             DestroyChildProcess(first.pr, first.useSigterm);
9124         }
9125         first.pr = NoProc;
9126     }
9127     if (second.reuse) {
9128         /* Put second chess program into idle state */
9129         if (second.pr != NoProc &&
9130             gameMode == TwoMachinesPlay) {
9131             SendToProgram("force\n", &second);
9132             if (second.usePing) {
9133               char buf[MSG_SIZ];
9134               sprintf(buf, "ping %d\n", ++second.lastPing);
9135               SendToProgram(buf, &second);
9136             }
9137         }
9138     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9139         /* Kill off second chess program */
9140         if (second.isr != NULL)
9141           RemoveInputSource(second.isr);
9142         second.isr = NULL;
9143
9144         if (second.pr != NoProc) {
9145             DoSleep( appData.delayBeforeQuit );
9146             SendToProgram("quit\n", &second);
9147             DoSleep( appData.delayAfterQuit );
9148             DestroyChildProcess(second.pr, second.useSigterm);
9149         }
9150         second.pr = NoProc;
9151     }
9152
9153     if (matchMode && gameMode == TwoMachinesPlay) {
9154         switch (result) {
9155         case WhiteWins:
9156           if (first.twoMachinesColor[0] == 'w') {
9157             first.matchWins++;
9158           } else {
9159             second.matchWins++;
9160           }
9161           break;
9162         case BlackWins:
9163           if (first.twoMachinesColor[0] == 'b') {
9164             first.matchWins++;
9165           } else {
9166             second.matchWins++;
9167           }
9168           break;
9169         default:
9170           break;
9171         }
9172         if (matchGame < appData.matchGames) {
9173             char *tmp;
9174             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9175                 tmp = first.twoMachinesColor;
9176                 first.twoMachinesColor = second.twoMachinesColor;
9177                 second.twoMachinesColor = tmp;
9178             }
9179             gameMode = nextGameMode;
9180             matchGame++;
9181             if(appData.matchPause>10000 || appData.matchPause<10)
9182                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9183             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9184             endingGame = 0; /* [HGM] crash */
9185             return;
9186         } else {
9187             char buf[MSG_SIZ];
9188             gameMode = nextGameMode;
9189             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9190                     first.tidy, second.tidy,
9191                     first.matchWins, second.matchWins,
9192                     appData.matchGames - (first.matchWins + second.matchWins));
9193             DisplayFatalError(buf, 0, 0);
9194         }
9195     }
9196     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9197         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9198       ExitAnalyzeMode();
9199     gameMode = nextGameMode;
9200     ModeHighlight();
9201     endingGame = 0;  /* [HGM] crash */
9202 }
9203
9204 /* Assumes program was just initialized (initString sent).
9205    Leaves program in force mode. */
9206 void
9207 FeedMovesToProgram(cps, upto)
9208      ChessProgramState *cps;
9209      int upto;
9210 {
9211     int i;
9212
9213     if (appData.debugMode)
9214       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9215               startedFromSetupPosition ? "position and " : "",
9216               backwardMostMove, upto, cps->which);
9217     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9218         // [HGM] variantswitch: make engine aware of new variant
9219         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9220                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9221         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9222         SendToProgram(buf, cps);
9223         currentlyInitializedVariant = gameInfo.variant;
9224     }
9225     SendToProgram("force\n", cps);
9226     if (startedFromSetupPosition) {
9227         SendBoard(cps, backwardMostMove);
9228     if (appData.debugMode) {
9229         fprintf(debugFP, "feedMoves\n");
9230     }
9231     }
9232     for (i = backwardMostMove; i < upto; i++) {
9233         SendMoveToProgram(i, cps);
9234     }
9235 }
9236
9237
9238 void
9239 ResurrectChessProgram()
9240 {
9241      /* The chess program may have exited.
9242         If so, restart it and feed it all the moves made so far. */
9243
9244     if (appData.noChessProgram || first.pr != NoProc) return;
9245
9246     StartChessProgram(&first);
9247     InitChessProgram(&first, FALSE);
9248     FeedMovesToProgram(&first, currentMove);
9249
9250     if (!first.sendTime) {
9251         /* can't tell gnuchess what its clock should read,
9252            so we bow to its notion. */
9253         ResetClocks();
9254         timeRemaining[0][currentMove] = whiteTimeRemaining;
9255         timeRemaining[1][currentMove] = blackTimeRemaining;
9256     }
9257
9258     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9259                 appData.icsEngineAnalyze) && first.analysisSupport) {
9260       SendToProgram("analyze\n", &first);
9261       first.analyzing = TRUE;
9262     }
9263 }
9264
9265 /*
9266  * Button procedures
9267  */
9268 void
9269 Reset(redraw, init)
9270      int redraw, init;
9271 {
9272     int i;
9273
9274     if (appData.debugMode) {
9275         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9276                 redraw, init, gameMode);
9277     }
9278     CleanupTail(); // [HGM] vari: delete any stored variations
9279     pausing = pauseExamInvalid = FALSE;
9280     startedFromSetupPosition = blackPlaysFirst = FALSE;
9281     firstMove = TRUE;
9282     whiteFlag = blackFlag = FALSE;
9283     userOfferedDraw = FALSE;
9284     hintRequested = bookRequested = FALSE;
9285     first.maybeThinking = FALSE;
9286     second.maybeThinking = FALSE;
9287     first.bookSuspend = FALSE; // [HGM] book
9288     second.bookSuspend = FALSE;
9289     thinkOutput[0] = NULLCHAR;
9290     lastHint[0] = NULLCHAR;
9291     ClearGameInfo(&gameInfo);
9292     gameInfo.variant = StringToVariant(appData.variant);
9293     ics_user_moved = ics_clock_paused = FALSE;
9294     ics_getting_history = H_FALSE;
9295     ics_gamenum = -1;
9296     white_holding[0] = black_holding[0] = NULLCHAR;
9297     ClearProgramStats();
9298     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9299
9300     ResetFrontEnd();
9301     ClearHighlights();
9302     flipView = appData.flipView;
9303     ClearPremoveHighlights();
9304     gotPremove = FALSE;
9305     alarmSounded = FALSE;
9306
9307     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9308     if(appData.serverMovesName != NULL) {
9309         /* [HGM] prepare to make moves file for broadcasting */
9310         clock_t t = clock();
9311         if(serverMoves != NULL) fclose(serverMoves);
9312         serverMoves = fopen(appData.serverMovesName, "r");
9313         if(serverMoves != NULL) {
9314             fclose(serverMoves);
9315             /* delay 15 sec before overwriting, so all clients can see end */
9316             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9317         }
9318         serverMoves = fopen(appData.serverMovesName, "w");
9319     }
9320
9321     ExitAnalyzeMode();
9322     gameMode = BeginningOfGame;
9323     ModeHighlight();
9324
9325     if(appData.icsActive) gameInfo.variant = VariantNormal;
9326     currentMove = forwardMostMove = backwardMostMove = 0;
9327     InitPosition(redraw);
9328     for (i = 0; i < MAX_MOVES; i++) {
9329         if (commentList[i] != NULL) {
9330             free(commentList[i]);
9331             commentList[i] = NULL;
9332         }
9333     }
9334
9335     ResetClocks();
9336     timeRemaining[0][0] = whiteTimeRemaining;
9337     timeRemaining[1][0] = blackTimeRemaining;
9338     if (first.pr == NULL) {
9339         StartChessProgram(&first);
9340     }
9341     if (init) {
9342             InitChessProgram(&first, startedFromSetupPosition);
9343     }
9344
9345     DisplayTitle("");
9346     DisplayMessage("", "");
9347     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9348     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9349     return;
9350 }
9351
9352 void
9353 AutoPlayGameLoop()
9354 {
9355     for (;;) {
9356         if (!AutoPlayOneMove())
9357           return;
9358         if (matchMode || appData.timeDelay == 0)
9359           continue;
9360         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9361           return;
9362         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9363         break;
9364     }
9365 }
9366
9367
9368 int
9369 AutoPlayOneMove()
9370 {
9371     int fromX, fromY, toX, toY;
9372
9373     if (appData.debugMode) {
9374       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9375     }
9376
9377     if (gameMode != PlayFromGameFile)
9378       return FALSE;
9379
9380     if (currentMove >= forwardMostMove) {
9381       gameMode = EditGame;
9382       ModeHighlight();
9383
9384       /* [AS] Clear current move marker at the end of a game */
9385       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9386
9387       return FALSE;
9388     }
9389
9390     toX = moveList[currentMove][2] - AAA;
9391     toY = moveList[currentMove][3] - ONE;
9392
9393     if (moveList[currentMove][1] == '@') {
9394         if (appData.highlightLastMove) {
9395             SetHighlights(-1, -1, toX, toY);
9396         }
9397     } else {
9398         fromX = moveList[currentMove][0] - AAA;
9399         fromY = moveList[currentMove][1] - ONE;
9400
9401         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9402
9403         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9404
9405         if (appData.highlightLastMove) {
9406             SetHighlights(fromX, fromY, toX, toY);
9407         }
9408     }
9409     DisplayMove(currentMove);
9410     SendMoveToProgram(currentMove++, &first);
9411     DisplayBothClocks();
9412     DrawPosition(FALSE, boards[currentMove]);
9413     // [HGM] PV info: always display, routine tests if empty
9414     DisplayComment(currentMove - 1, commentList[currentMove]);
9415     return TRUE;
9416 }
9417
9418
9419 int
9420 LoadGameOneMove(readAhead)
9421      ChessMove readAhead;
9422 {
9423     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9424     char promoChar = NULLCHAR;
9425     ChessMove moveType;
9426     char move[MSG_SIZ];
9427     char *p, *q;
9428
9429     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9430         gameMode != AnalyzeMode && gameMode != Training) {
9431         gameFileFP = NULL;
9432         return FALSE;
9433     }
9434
9435     yyboardindex = forwardMostMove;
9436     if (readAhead != (ChessMove)0) {
9437       moveType = readAhead;
9438     } else {
9439       if (gameFileFP == NULL)
9440           return FALSE;
9441       moveType = (ChessMove) yylex();
9442     }
9443
9444     done = FALSE;
9445     switch (moveType) {
9446       case Comment:
9447         if (appData.debugMode)
9448           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9449         p = yy_text;
9450
9451         /* append the comment but don't display it */
9452         AppendComment(currentMove, p, FALSE);
9453         return TRUE;
9454
9455       case WhiteCapturesEnPassant:
9456       case BlackCapturesEnPassant:
9457       case WhitePromotionChancellor:
9458       case BlackPromotionChancellor:
9459       case WhitePromotionArchbishop:
9460       case BlackPromotionArchbishop:
9461       case WhitePromotionCentaur:
9462       case BlackPromotionCentaur:
9463       case WhitePromotionQueen:
9464       case BlackPromotionQueen:
9465       case WhitePromotionRook:
9466       case BlackPromotionRook:
9467       case WhitePromotionBishop:
9468       case BlackPromotionBishop:
9469       case WhitePromotionKnight:
9470       case BlackPromotionKnight:
9471       case WhitePromotionKing:
9472       case BlackPromotionKing:
9473       case NormalMove:
9474       case WhiteKingSideCastle:
9475       case WhiteQueenSideCastle:
9476       case BlackKingSideCastle:
9477       case BlackQueenSideCastle:
9478       case WhiteKingSideCastleWild:
9479       case WhiteQueenSideCastleWild:
9480       case BlackKingSideCastleWild:
9481       case BlackQueenSideCastleWild:
9482       /* PUSH Fabien */
9483       case WhiteHSideCastleFR:
9484       case WhiteASideCastleFR:
9485       case BlackHSideCastleFR:
9486       case BlackASideCastleFR:
9487       /* POP Fabien */
9488         if (appData.debugMode)
9489           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9490         fromX = currentMoveString[0] - AAA;
9491         fromY = currentMoveString[1] - ONE;
9492         toX = currentMoveString[2] - AAA;
9493         toY = currentMoveString[3] - ONE;
9494         promoChar = currentMoveString[4];
9495         break;
9496
9497       case WhiteDrop:
9498       case BlackDrop:
9499         if (appData.debugMode)
9500           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9501         fromX = moveType == WhiteDrop ?
9502           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9503         (int) CharToPiece(ToLower(currentMoveString[0]));
9504         fromY = DROP_RANK;
9505         toX = currentMoveString[2] - AAA;
9506         toY = currentMoveString[3] - ONE;
9507         break;
9508
9509       case WhiteWins:
9510       case BlackWins:
9511       case GameIsDrawn:
9512       case GameUnfinished:
9513         if (appData.debugMode)
9514           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9515         p = strchr(yy_text, '{');
9516         if (p == NULL) p = strchr(yy_text, '(');
9517         if (p == NULL) {
9518             p = yy_text;
9519             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9520         } else {
9521             q = strchr(p, *p == '{' ? '}' : ')');
9522             if (q != NULL) *q = NULLCHAR;
9523             p++;
9524         }
9525         GameEnds(moveType, p, GE_FILE);
9526         done = TRUE;
9527         if (cmailMsgLoaded) {
9528             ClearHighlights();
9529             flipView = WhiteOnMove(currentMove);
9530             if (moveType == GameUnfinished) flipView = !flipView;
9531             if (appData.debugMode)
9532               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9533         }
9534         break;
9535
9536       case (ChessMove) 0:       /* end of file */
9537         if (appData.debugMode)
9538           fprintf(debugFP, "Parser hit end of file\n");
9539         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9540           case MT_NONE:
9541           case MT_CHECK:
9542             break;
9543           case MT_CHECKMATE:
9544           case MT_STAINMATE:
9545             if (WhiteOnMove(currentMove)) {
9546                 GameEnds(BlackWins, "Black mates", GE_FILE);
9547             } else {
9548                 GameEnds(WhiteWins, "White mates", GE_FILE);
9549             }
9550             break;
9551           case MT_STALEMATE:
9552             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9553             break;
9554         }
9555         done = TRUE;
9556         break;
9557
9558       case MoveNumberOne:
9559         if (lastLoadGameStart == GNUChessGame) {
9560             /* GNUChessGames have numbers, but they aren't move numbers */
9561             if (appData.debugMode)
9562               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9563                       yy_text, (int) moveType);
9564             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9565         }
9566         /* else fall thru */
9567
9568       case XBoardGame:
9569       case GNUChessGame:
9570       case PGNTag:
9571         /* Reached start of next game in file */
9572         if (appData.debugMode)
9573           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9574         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9575           case MT_NONE:
9576           case MT_CHECK:
9577             break;
9578           case MT_CHECKMATE:
9579           case MT_STAINMATE:
9580             if (WhiteOnMove(currentMove)) {
9581                 GameEnds(BlackWins, "Black mates", GE_FILE);
9582             } else {
9583                 GameEnds(WhiteWins, "White mates", GE_FILE);
9584             }
9585             break;
9586           case MT_STALEMATE:
9587             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9588             break;
9589         }
9590         done = TRUE;
9591         break;
9592
9593       case PositionDiagram:     /* should not happen; ignore */
9594       case ElapsedTime:         /* ignore */
9595       case NAG:                 /* ignore */
9596         if (appData.debugMode)
9597           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9598                   yy_text, (int) moveType);
9599         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9600
9601       case IllegalMove:
9602         if (appData.testLegality) {
9603             if (appData.debugMode)
9604               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9605             sprintf(move, _("Illegal move: %d.%s%s"),
9606                     (forwardMostMove / 2) + 1,
9607                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9608             DisplayError(move, 0);
9609             done = TRUE;
9610         } else {
9611             if (appData.debugMode)
9612               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9613                       yy_text, currentMoveString);
9614             fromX = currentMoveString[0] - AAA;
9615             fromY = currentMoveString[1] - ONE;
9616             toX = currentMoveString[2] - AAA;
9617             toY = currentMoveString[3] - ONE;
9618             promoChar = currentMoveString[4];
9619         }
9620         break;
9621
9622       case AmbiguousMove:
9623         if (appData.debugMode)
9624           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9625         sprintf(move, _("Ambiguous move: %d.%s%s"),
9626                 (forwardMostMove / 2) + 1,
9627                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9628         DisplayError(move, 0);
9629         done = TRUE;
9630         break;
9631
9632       default:
9633       case ImpossibleMove:
9634         if (appData.debugMode)
9635           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9636         sprintf(move, _("Illegal move: %d.%s%s"),
9637                 (forwardMostMove / 2) + 1,
9638                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9639         DisplayError(move, 0);
9640         done = TRUE;
9641         break;
9642     }
9643
9644     if (done) {
9645         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9646             DrawPosition(FALSE, boards[currentMove]);
9647             DisplayBothClocks();
9648             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9649               DisplayComment(currentMove - 1, commentList[currentMove]);
9650         }
9651         (void) StopLoadGameTimer();
9652         gameFileFP = NULL;
9653         cmailOldMove = forwardMostMove;
9654         return FALSE;
9655     } else {
9656         /* currentMoveString is set as a side-effect of yylex */
9657         strcat(currentMoveString, "\n");
9658         strcpy(moveList[forwardMostMove], currentMoveString);
9659
9660         thinkOutput[0] = NULLCHAR;
9661         MakeMove(fromX, fromY, toX, toY, promoChar);
9662         currentMove = forwardMostMove;
9663         return TRUE;
9664     }
9665 }
9666
9667 /* Load the nth game from the given file */
9668 int
9669 LoadGameFromFile(filename, n, title, useList)
9670      char *filename;
9671      int n;
9672      char *title;
9673      /*Boolean*/ int useList;
9674 {
9675     FILE *f;
9676     char buf[MSG_SIZ];
9677
9678     if (strcmp(filename, "-") == 0) {
9679         f = stdin;
9680         title = "stdin";
9681     } else {
9682         f = fopen(filename, "rb");
9683         if (f == NULL) {
9684           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9685             DisplayError(buf, errno);
9686             return FALSE;
9687         }
9688     }
9689     if (fseek(f, 0, 0) == -1) {
9690         /* f is not seekable; probably a pipe */
9691         useList = FALSE;
9692     }
9693     if (useList && n == 0) {
9694         int error = GameListBuild(f);
9695         if (error) {
9696             DisplayError(_("Cannot build game list"), error);
9697         } else if (!ListEmpty(&gameList) &&
9698                    ((ListGame *) gameList.tailPred)->number > 1) {
9699           // TODO convert to GTK
9700           //        GameListPopUp(f, title);
9701             return TRUE;
9702         }
9703         GameListDestroy();
9704         n = 1;
9705     }
9706     if (n == 0) n = 1;
9707     return LoadGame(f, n, title, FALSE);
9708 }
9709
9710
9711 void
9712 MakeRegisteredMove()
9713 {
9714     int fromX, fromY, toX, toY;
9715     char promoChar;
9716     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9717         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9718           case CMAIL_MOVE:
9719           case CMAIL_DRAW:
9720             if (appData.debugMode)
9721               fprintf(debugFP, "Restoring %s for game %d\n",
9722                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9723
9724             thinkOutput[0] = NULLCHAR;
9725             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9726             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9727             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9728             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9729             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9730             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9731             MakeMove(fromX, fromY, toX, toY, promoChar);
9732             ShowMove(fromX, fromY, toX, toY);
9733             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9734               case MT_NONE:
9735               case MT_CHECK:
9736                 break;
9737
9738               case MT_CHECKMATE:
9739               case MT_STAINMATE:
9740                 if (WhiteOnMove(currentMove)) {
9741                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9742                 } else {
9743                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9744                 }
9745                 break;
9746
9747               case MT_STALEMATE:
9748                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9749                 break;
9750             }
9751
9752             break;
9753
9754           case CMAIL_RESIGN:
9755             if (WhiteOnMove(currentMove)) {
9756                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9757             } else {
9758                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9759             }
9760             break;
9761
9762           case CMAIL_ACCEPT:
9763             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9764             break;
9765
9766           default:
9767             break;
9768         }
9769     }
9770
9771     return;
9772 }
9773
9774 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9775 int
9776 CmailLoadGame(f, gameNumber, title, useList)
9777      FILE *f;
9778      int gameNumber;
9779      char *title;
9780      int useList;
9781 {
9782     int retVal;
9783
9784     if (gameNumber > nCmailGames) {
9785         DisplayError(_("No more games in this message"), 0);
9786         return FALSE;
9787     }
9788     if (f == lastLoadGameFP) {
9789         int offset = gameNumber - lastLoadGameNumber;
9790         if (offset == 0) {
9791             cmailMsg[0] = NULLCHAR;
9792             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9793                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9794                 nCmailMovesRegistered--;
9795             }
9796             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9797             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9798                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9799             }
9800         } else {
9801             if (! RegisterMove()) return FALSE;
9802         }
9803     }
9804
9805     retVal = LoadGame(f, gameNumber, title, useList);
9806
9807     /* Make move registered during previous look at this game, if any */
9808     MakeRegisteredMove();
9809
9810     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9811         commentList[currentMove]
9812           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9813         DisplayComment(currentMove - 1, commentList[currentMove]);
9814     }
9815
9816     return retVal;
9817 }
9818
9819 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9820 int
9821 ReloadGame(offset)
9822      int offset;
9823 {
9824     int gameNumber = lastLoadGameNumber + offset;
9825     if (lastLoadGameFP == NULL) {
9826         DisplayError(_("No game has been loaded yet"), 0);
9827         return FALSE;
9828     }
9829     if (gameNumber <= 0) {
9830         DisplayError(_("Can't back up any further"), 0);
9831         return FALSE;
9832     }
9833     if (cmailMsgLoaded) {
9834         return CmailLoadGame(lastLoadGameFP, gameNumber,
9835                              lastLoadGameTitle, lastLoadGameUseList);
9836     } else {
9837         return LoadGame(lastLoadGameFP, gameNumber,
9838                         lastLoadGameTitle, lastLoadGameUseList);
9839     }
9840 }
9841
9842
9843
9844 /* Load the nth game from open file f */
9845 int
9846 LoadGame(f, gameNumber, title, useList)
9847      FILE *f;
9848      int gameNumber;
9849      char *title;
9850      int useList;
9851 {
9852     ChessMove cm;
9853     char buf[MSG_SIZ];
9854     int gn = gameNumber;
9855     ListGame *lg = NULL;
9856     int numPGNTags = 0;
9857     int err;
9858     GameMode oldGameMode;
9859     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9860
9861     if (appData.debugMode)
9862         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9863
9864     if (gameMode == Training )
9865         SetTrainingModeOff();
9866
9867     oldGameMode = gameMode;
9868     if (gameMode != BeginningOfGame) 
9869       {
9870         Reset(FALSE, TRUE);
9871       };
9872
9873     gameFileFP = f;
9874     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9875       {
9876         fclose(lastLoadGameFP);
9877       };
9878
9879     if (useList) 
9880       {
9881         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9882         
9883         if (lg) 
9884           {
9885             fseek(f, lg->offset, 0);
9886             GameListHighlight(gameNumber);
9887             gn = 1;
9888           }
9889         else 
9890           {
9891             DisplayError(_("Game number out of range"), 0);
9892             return FALSE;
9893           };
9894       } 
9895     else 
9896       {
9897         GameListDestroy();
9898         if (fseek(f, 0, 0) == -1) 
9899           {
9900             if (f == lastLoadGameFP ?
9901                 gameNumber == lastLoadGameNumber + 1 :
9902                 gameNumber == 1) 
9903               {
9904                 gn = 1;
9905               } 
9906             else 
9907               {
9908                 DisplayError(_("Can't seek on game file"), 0);
9909                 return FALSE;
9910               };
9911           };
9912       };
9913
9914     lastLoadGameFP      = f;
9915     lastLoadGameNumber  = gameNumber;
9916     strcpy(lastLoadGameTitle, title);
9917     lastLoadGameUseList = useList;
9918
9919     yynewfile(f);
9920
9921     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9922       {
9923         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9924                  lg->gameInfo.black);
9925         DisplayTitle(buf);
9926       } 
9927     else if (*title != NULLCHAR) 
9928       {
9929         if (gameNumber > 1) 
9930           {
9931             sprintf(buf, "%s %d", title, gameNumber);
9932             DisplayTitle(buf);
9933           } 
9934         else 
9935           {
9936             DisplayTitle(title);
9937           };
9938       };
9939
9940     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9941       {
9942         gameMode = PlayFromGameFile;
9943         ModeHighlight();
9944       };
9945
9946     currentMove = forwardMostMove = backwardMostMove = 0;
9947     CopyBoard(boards[0], initialPosition);
9948     StopClocks();
9949
9950     /*
9951      * Skip the first gn-1 games in the file.
9952      * Also skip over anything that precedes an identifiable
9953      * start of game marker, to avoid being confused by
9954      * garbage at the start of the file.  Currently
9955      * recognized start of game markers are the move number "1",
9956      * the pattern "gnuchess .* game", the pattern
9957      * "^[#;%] [^ ]* game file", and a PGN tag block.
9958      * A game that starts with one of the latter two patterns
9959      * will also have a move number 1, possibly
9960      * following a position diagram.
9961      * 5-4-02: Let's try being more lenient and allowing a game to
9962      * start with an unnumbered move.  Does that break anything?
9963      */
9964     cm = lastLoadGameStart = (ChessMove) 0;
9965     while (gn > 0) {
9966         yyboardindex = forwardMostMove;
9967         cm = (ChessMove) yylex();
9968         switch (cm) {
9969           case (ChessMove) 0:
9970             if (cmailMsgLoaded) {
9971                 nCmailGames = CMAIL_MAX_GAMES - gn;
9972             } else {
9973                 Reset(TRUE, TRUE);
9974                 DisplayError(_("Game not found in file"), 0);
9975             }
9976             return FALSE;
9977
9978           case GNUChessGame:
9979           case XBoardGame:
9980             gn--;
9981             lastLoadGameStart = cm;
9982             break;
9983
9984           case MoveNumberOne:
9985             switch (lastLoadGameStart) {
9986               case GNUChessGame:
9987               case XBoardGame:
9988               case PGNTag:
9989                 break;
9990               case MoveNumberOne:
9991               case (ChessMove) 0:
9992                 gn--;           /* count this game */
9993                 lastLoadGameStart = cm;
9994                 break;
9995               default:
9996                 /* impossible */
9997                 break;
9998             }
9999             break;
10000
10001           case PGNTag:
10002             switch (lastLoadGameStart) {
10003               case GNUChessGame:
10004               case PGNTag:
10005               case MoveNumberOne:
10006               case (ChessMove) 0:
10007                 gn--;           /* count this game */
10008                 lastLoadGameStart = cm;
10009                 break;
10010               case XBoardGame:
10011                 lastLoadGameStart = cm; /* game counted already */
10012                 break;
10013               default:
10014                 /* impossible */
10015                 break;
10016             }
10017             if (gn > 0) {
10018                 do {
10019                     yyboardindex = forwardMostMove;
10020                     cm = (ChessMove) yylex();
10021                 } while (cm == PGNTag || cm == Comment);
10022             }
10023             break;
10024
10025           case WhiteWins:
10026           case BlackWins:
10027           case GameIsDrawn:
10028             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10029                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10030                     != CMAIL_OLD_RESULT) {
10031                     nCmailResults ++ ;
10032                     cmailResult[  CMAIL_MAX_GAMES
10033                                 - gn - 1] = CMAIL_OLD_RESULT;
10034                 }
10035             }
10036             break;
10037
10038           case NormalMove:
10039             /* Only a NormalMove can be at the start of a game
10040              * without a position diagram. */
10041             if (lastLoadGameStart == (ChessMove) 0) {
10042               gn--;
10043               lastLoadGameStart = MoveNumberOne;
10044             }
10045             break;
10046
10047           default:
10048             break;
10049         }
10050     }
10051
10052     if (appData.debugMode)
10053       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10054
10055     if (cm == XBoardGame) {
10056         /* Skip any header junk before position diagram and/or move 1 */
10057         for (;;) {
10058             yyboardindex = forwardMostMove;
10059             cm = (ChessMove) yylex();
10060
10061             if (cm == (ChessMove) 0 ||
10062                 cm == GNUChessGame || cm == XBoardGame) {
10063                 /* Empty game; pretend end-of-file and handle later */
10064                 cm = (ChessMove) 0;
10065                 break;
10066             }
10067
10068             if (cm == MoveNumberOne || cm == PositionDiagram ||
10069                 cm == PGNTag || cm == Comment)
10070               break;
10071         }
10072     } else if (cm == GNUChessGame) {
10073         if (gameInfo.event != NULL) {
10074             free(gameInfo.event);
10075         }
10076         gameInfo.event = StrSave(yy_text);
10077     }
10078
10079     startedFromSetupPosition = FALSE;
10080     while (cm == PGNTag) {
10081         if (appData.debugMode)
10082           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10083         err = ParsePGNTag(yy_text, &gameInfo);
10084         if (!err) numPGNTags++;
10085
10086         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10087         if(gameInfo.variant != oldVariant) {
10088             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10089             InitPosition(TRUE);
10090             oldVariant = gameInfo.variant;
10091             if (appData.debugMode)
10092               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10093         }
10094
10095
10096         if (gameInfo.fen != NULL) {
10097           Board initial_position;
10098           startedFromSetupPosition = TRUE;
10099           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10100             Reset(TRUE, TRUE);
10101             DisplayError(_("Bad FEN position in file"), 0);
10102             return FALSE;
10103           }
10104           CopyBoard(boards[0], initial_position);
10105           if (blackPlaysFirst) {
10106             currentMove = forwardMostMove = backwardMostMove = 1;
10107             CopyBoard(boards[1], initial_position);
10108             strcpy(moveList[0], "");
10109             strcpy(parseList[0], "");
10110             timeRemaining[0][1] = whiteTimeRemaining;
10111             timeRemaining[1][1] = blackTimeRemaining;
10112             if (commentList[0] != NULL) {
10113               commentList[1] = commentList[0];
10114               commentList[0] = NULL;
10115             }
10116           } else {
10117             currentMove = forwardMostMove = backwardMostMove = 0;
10118           }
10119           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10120           {   int i;
10121               initialRulePlies = FENrulePlies;
10122               for( i=0; i< nrCastlingRights; i++ )
10123                   initialRights[i] = initial_position[CASTLING][i];
10124           }
10125           yyboardindex = forwardMostMove;
10126           free(gameInfo.fen);
10127           gameInfo.fen = NULL;
10128         }
10129
10130         yyboardindex = forwardMostMove;
10131         cm = (ChessMove) yylex();
10132
10133         /* Handle comments interspersed among the tags */
10134         while (cm == Comment) {
10135             char *p;
10136             if (appData.debugMode)
10137               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10138             p = yy_text;
10139             AppendComment(currentMove, p, FALSE);
10140             yyboardindex = forwardMostMove;
10141             cm = (ChessMove) yylex();
10142         }
10143     }
10144
10145     /* don't rely on existence of Event tag since if game was
10146      * pasted from clipboard the Event tag may not exist
10147      */
10148     if (numPGNTags > 0){
10149         char *tags;
10150         if (gameInfo.variant == VariantNormal) {
10151           gameInfo.variant = StringToVariant(gameInfo.event);
10152         }
10153         if (!matchMode) {
10154           if( appData.autoDisplayTags ) {
10155             tags = PGNTags(&gameInfo);
10156             TagsPopUp(tags, CmailMsg());
10157             free(tags);
10158           }
10159         }
10160     } else {
10161         /* Make something up, but don't display it now */
10162         SetGameInfo();
10163         TagsPopDown();
10164     }
10165
10166     if (cm == PositionDiagram) {
10167         int i, j;
10168         char *p;
10169         Board initial_position;
10170
10171         if (appData.debugMode)
10172           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10173
10174         if (!startedFromSetupPosition) {
10175             p = yy_text;
10176             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10177               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10178                 switch (*p) {
10179                   case '[':
10180                   case '-':
10181                   case ' ':
10182                   case '\t':
10183                   case '\n':
10184                   case '\r':
10185                     break;
10186                   default:
10187                     initial_position[i][j++] = CharToPiece(*p);
10188                     break;
10189                 }
10190             while (*p == ' ' || *p == '\t' ||
10191                    *p == '\n' || *p == '\r') p++;
10192
10193             if (strncmp(p, "black", strlen("black"))==0)
10194               blackPlaysFirst = TRUE;
10195             else
10196               blackPlaysFirst = FALSE;
10197             startedFromSetupPosition = TRUE;
10198
10199             CopyBoard(boards[0], initial_position);
10200             if (blackPlaysFirst) {
10201                 currentMove = forwardMostMove = backwardMostMove = 1;
10202                 CopyBoard(boards[1], initial_position);
10203                 strcpy(moveList[0], "");
10204                 strcpy(parseList[0], "");
10205                 timeRemaining[0][1] = whiteTimeRemaining;
10206                 timeRemaining[1][1] = blackTimeRemaining;
10207                 if (commentList[0] != NULL) {
10208                     commentList[1] = commentList[0];
10209                     commentList[0] = NULL;
10210                 }
10211             } else {
10212                 currentMove = forwardMostMove = backwardMostMove = 0;
10213             }
10214         }
10215         yyboardindex = forwardMostMove;
10216         cm = (ChessMove) yylex();
10217     }
10218
10219     if (first.pr == NoProc) {
10220         StartChessProgram(&first);
10221     }
10222     InitChessProgram(&first, FALSE);
10223     SendToProgram("force\n", &first);
10224     if (startedFromSetupPosition) {
10225         SendBoard(&first, forwardMostMove);
10226     if (appData.debugMode) {
10227         fprintf(debugFP, "Load Game\n");
10228     }
10229         DisplayBothClocks();
10230     }
10231
10232     /* [HGM] server: flag to write setup moves in broadcast file as one */
10233     loadFlag = appData.suppressLoadMoves;
10234
10235     while (cm == Comment) {
10236         char *p;
10237         if (appData.debugMode)
10238           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10239         p = yy_text;
10240         AppendComment(currentMove, p, FALSE);
10241         yyboardindex = forwardMostMove;
10242         cm = (ChessMove) yylex();
10243     }
10244
10245     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10246         cm == WhiteWins || cm == BlackWins ||
10247         cm == GameIsDrawn || cm == GameUnfinished) {
10248         DisplayMessage("", _("No moves in game"));
10249         if (cmailMsgLoaded) {
10250             if (appData.debugMode)
10251               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10252             ClearHighlights();
10253             flipView = FALSE;
10254         }
10255         DrawPosition(FALSE, boards[currentMove]);
10256         DisplayBothClocks();
10257         gameMode = EditGame;
10258         ModeHighlight();
10259         gameFileFP = NULL;
10260         cmailOldMove = 0;
10261         return TRUE;
10262     }
10263
10264     // [HGM] PV info: routine tests if comment empty
10265     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10266         DisplayComment(currentMove - 1, commentList[currentMove]);
10267     }
10268     if (!matchMode && appData.timeDelay != 0)
10269       DrawPosition(FALSE, boards[currentMove]);
10270
10271     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10272       programStats.ok_to_send = 1;
10273     }
10274
10275     /* if the first token after the PGN tags is a move
10276      * and not move number 1, retrieve it from the parser
10277      */
10278     if (cm != MoveNumberOne)
10279         LoadGameOneMove(cm);
10280
10281     /* load the remaining moves from the file */
10282     while (LoadGameOneMove((ChessMove)0)) {
10283       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10284       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10285     }
10286
10287     /* rewind to the start of the game */
10288     currentMove = backwardMostMove;
10289
10290     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10291
10292     if (oldGameMode == AnalyzeFile ||
10293         oldGameMode == AnalyzeMode) {
10294       AnalyzeFileEvent();
10295     }
10296
10297     if (matchMode || appData.timeDelay == 0) {
10298       ToEndEvent();
10299       gameMode = EditGame;
10300       ModeHighlight();
10301     } else if (appData.timeDelay > 0) {
10302       AutoPlayGameLoop();
10303     }
10304
10305     if (appData.debugMode)
10306         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10307
10308     loadFlag = 0; /* [HGM] true game starts */
10309     return TRUE;
10310 }
10311
10312 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10313 int
10314 ReloadPosition(offset)
10315      int offset;
10316 {
10317     int positionNumber = lastLoadPositionNumber + offset;
10318     if (lastLoadPositionFP == NULL) {
10319         DisplayError(_("No position has been loaded yet"), 0);
10320         return FALSE;
10321     }
10322     if (positionNumber <= 0) {
10323         DisplayError(_("Can't back up any further"), 0);
10324         return FALSE;
10325     }
10326     return LoadPosition(lastLoadPositionFP, positionNumber,
10327                         lastLoadPositionTitle);
10328 }
10329
10330 /* Load the nth position from the given file */
10331 int
10332 LoadPositionFromFile(filename, n, title)
10333      char *filename;
10334      int n;
10335      char *title;
10336 {
10337     FILE *f;
10338     char buf[MSG_SIZ];
10339
10340     if (strcmp(filename, "-") == 0) {
10341         return LoadPosition(stdin, n, "stdin");
10342     } else {
10343         f = fopen(filename, "rb");
10344         if (f == NULL) {
10345             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10346             DisplayError(buf, errno);
10347             return FALSE;
10348         } else {
10349             return LoadPosition(f, n, title);
10350         }
10351     }
10352 }
10353
10354 /* Load the nth position from the given open file, and close it */
10355 int
10356 LoadPosition(f, positionNumber, title)
10357      FILE *f;
10358      int positionNumber;
10359      char *title;
10360 {
10361     char *p, line[MSG_SIZ];
10362     Board initial_position;
10363     int i, j, fenMode, pn;
10364
10365     if (gameMode == Training )
10366         SetTrainingModeOff();
10367
10368     if (gameMode != BeginningOfGame) {
10369         Reset(FALSE, TRUE);
10370     }
10371     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10372         fclose(lastLoadPositionFP);
10373     }
10374     if (positionNumber == 0) positionNumber = 1;
10375     lastLoadPositionFP = f;
10376     lastLoadPositionNumber = positionNumber;
10377     strcpy(lastLoadPositionTitle, title);
10378     if (first.pr == NoProc) {
10379       StartChessProgram(&first);
10380       InitChessProgram(&first, FALSE);
10381     }
10382     pn = positionNumber;
10383     if (positionNumber < 0) {
10384         /* Negative position number means to seek to that byte offset */
10385         if (fseek(f, -positionNumber, 0) == -1) {
10386             DisplayError(_("Can't seek on position file"), 0);
10387             return FALSE;
10388         };
10389         pn = 1;
10390     } else {
10391         if (fseek(f, 0, 0) == -1) {
10392             if (f == lastLoadPositionFP ?
10393                 positionNumber == lastLoadPositionNumber + 1 :
10394                 positionNumber == 1) {
10395                 pn = 1;
10396             } else {
10397                 DisplayError(_("Can't seek on position file"), 0);
10398                 return FALSE;
10399             }
10400         }
10401     }
10402     /* See if this file is FEN or old-style xboard */
10403     if (fgets(line, MSG_SIZ, f) == NULL) {
10404         DisplayError(_("Position not found in file"), 0);
10405         return FALSE;
10406     }
10407     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10408     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10409
10410     if (pn >= 2) {
10411         if (fenMode || line[0] == '#') pn--;
10412         while (pn > 0) {
10413             /* skip positions before number pn */
10414             if (fgets(line, MSG_SIZ, f) == NULL) {
10415                 Reset(TRUE, TRUE);
10416                 DisplayError(_("Position not found in file"), 0);
10417                 return FALSE;
10418             }
10419             if (fenMode || line[0] == '#') pn--;
10420         }
10421     }
10422
10423     if (fenMode) {
10424         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10425             DisplayError(_("Bad FEN position in file"), 0);
10426             return FALSE;
10427         }
10428     } else {
10429         (void) fgets(line, MSG_SIZ, f);
10430         (void) fgets(line, MSG_SIZ, f);
10431
10432         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10433             (void) fgets(line, MSG_SIZ, f);
10434             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10435                 if (*p == ' ')
10436                   continue;
10437                 initial_position[i][j++] = CharToPiece(*p);
10438             }
10439         }
10440
10441         blackPlaysFirst = FALSE;
10442         if (!feof(f)) {
10443             (void) fgets(line, MSG_SIZ, f);
10444             if (strncmp(line, "black", strlen("black"))==0)
10445               blackPlaysFirst = TRUE;
10446         }
10447     }
10448     startedFromSetupPosition = TRUE;
10449
10450     SendToProgram("force\n", &first);
10451     CopyBoard(boards[0], initial_position);
10452     if (blackPlaysFirst) {
10453         currentMove = forwardMostMove = backwardMostMove = 1;
10454         strcpy(moveList[0], "");
10455         strcpy(parseList[0], "");
10456         CopyBoard(boards[1], initial_position);
10457         DisplayMessage("", _("Black to play"));
10458     } else {
10459         currentMove = forwardMostMove = backwardMostMove = 0;
10460         DisplayMessage("", _("White to play"));
10461     }
10462     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10463     SendBoard(&first, forwardMostMove);
10464     if (appData.debugMode) {
10465 int i, j;
10466   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10467   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10468         fprintf(debugFP, "Load Position\n");
10469     }
10470
10471     if (positionNumber > 1) {
10472         sprintf(line, "%s %d", title, positionNumber);
10473         DisplayTitle(line);
10474     } else {
10475         DisplayTitle(title);
10476     }
10477     gameMode = EditGame;
10478     ModeHighlight();
10479     ResetClocks();
10480     timeRemaining[0][1] = whiteTimeRemaining;
10481     timeRemaining[1][1] = blackTimeRemaining;
10482     DrawPosition(FALSE, boards[currentMove]);
10483
10484     return TRUE;
10485 }
10486
10487
10488 void
10489 CopyPlayerNameIntoFileName(dest, src)
10490      char **dest, *src;
10491 {
10492     while (*src != NULLCHAR && *src != ',') {
10493         if (*src == ' ') {
10494             *(*dest)++ = '_';
10495             src++;
10496         } else {
10497             *(*dest)++ = *src++;
10498         }
10499     }
10500 }
10501
10502 char *DefaultFileName(ext)
10503      char *ext;
10504 {
10505     static char def[MSG_SIZ];
10506     char *p;
10507
10508     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10509         p = def;
10510         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10511         *p++ = '-';
10512         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10513         *p++ = '.';
10514         strcpy(p, ext);
10515     } else {
10516         def[0] = NULLCHAR;
10517     }
10518     return def;
10519 }
10520
10521 /* Save the current game to the given file */
10522 int
10523 SaveGameToFile(filename, append)
10524      char *filename;
10525      int append;
10526 {
10527     FILE *f;
10528     char buf[MSG_SIZ];
10529
10530     if (strcmp(filename, "-") == 0) {
10531         return SaveGame(stdout, 0, NULL);
10532     } else {
10533         f = fopen(filename, append ? "a" : "w");
10534         if (f == NULL) {
10535             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10536             DisplayError(buf, errno);
10537             return FALSE;
10538         } else {
10539             return SaveGame(f, 0, NULL);
10540         }
10541     }
10542 }
10543
10544 char *
10545 SavePart(str)
10546      char *str;
10547 {
10548     static char buf[MSG_SIZ];
10549     char *p;
10550
10551     p = strchr(str, ' ');
10552     if (p == NULL) return str;
10553     strncpy(buf, str, p - str);
10554     buf[p - str] = NULLCHAR;
10555     return buf;
10556 }
10557
10558 #define PGN_MAX_LINE 75
10559
10560 #define PGN_SIDE_WHITE  0
10561 #define PGN_SIDE_BLACK  1
10562
10563 /* [AS] */
10564 static int FindFirstMoveOutOfBook( int side )
10565 {
10566     int result = -1;
10567
10568     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10569         int index = backwardMostMove;
10570         int has_book_hit = 0;
10571
10572         if( (index % 2) != side ) {
10573             index++;
10574         }
10575
10576         while( index < forwardMostMove ) {
10577             /* Check to see if engine is in book */
10578             int depth = pvInfoList[index].depth;
10579             int score = pvInfoList[index].score;
10580             int in_book = 0;
10581
10582             if( depth <= 2 ) {
10583                 in_book = 1;
10584             }
10585             else if( score == 0 && depth == 63 ) {
10586                 in_book = 1; /* Zappa */
10587             }
10588             else if( score == 2 && depth == 99 ) {
10589                 in_book = 1; /* Abrok */
10590             }
10591
10592             has_book_hit += in_book;
10593
10594             if( ! in_book ) {
10595                 result = index;
10596
10597                 break;
10598             }
10599
10600             index += 2;
10601         }
10602     }
10603
10604     return result;
10605 }
10606
10607 /* [AS] */
10608 void GetOutOfBookInfo( char * buf )
10609 {
10610     int oob[2];
10611     int i;
10612     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10613
10614     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10615     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10616
10617     *buf = '\0';
10618
10619     if( oob[0] >= 0 || oob[1] >= 0 ) {
10620         for( i=0; i<2; i++ ) {
10621             int idx = oob[i];
10622
10623             if( idx >= 0 ) {
10624                 if( i > 0 && oob[0] >= 0 ) {
10625                     strcat( buf, "   " );
10626                 }
10627
10628                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10629                 sprintf( buf+strlen(buf), "%s%.2f",
10630                     pvInfoList[idx].score >= 0 ? "+" : "",
10631                     pvInfoList[idx].score / 100.0 );
10632             }
10633         }
10634     }
10635 }
10636
10637 /* Save game in PGN style and close the file */
10638 int
10639 SaveGamePGN(f)
10640      FILE *f;
10641 {
10642     int i, offset, linelen, newblock;
10643     time_t tm;
10644 //    char *movetext;
10645     char numtext[32];
10646     int movelen, numlen, blank;
10647     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10648
10649     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10650
10651     tm = time((time_t *) NULL);
10652
10653     PrintPGNTags(f, &gameInfo);
10654
10655     if (backwardMostMove > 0 || startedFromSetupPosition) {
10656         char *fen = PositionToFEN(backwardMostMove, NULL);
10657         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10658         fprintf(f, "\n{--------------\n");
10659         PrintPosition(f, backwardMostMove);
10660         fprintf(f, "--------------}\n");
10661         free(fen);
10662     }
10663     else {
10664         /* [AS] Out of book annotation */
10665         if( appData.saveOutOfBookInfo ) {
10666             char buf[64];
10667
10668             GetOutOfBookInfo( buf );
10669
10670             if( buf[0] != '\0' ) {
10671                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10672             }
10673         }
10674
10675         fprintf(f, "\n");
10676     }
10677
10678     i = backwardMostMove;
10679     linelen = 0;
10680     newblock = TRUE;
10681
10682     while (i < forwardMostMove) {
10683         /* Print comments preceding this move */
10684         if (commentList[i] != NULL) {
10685             if (linelen > 0) fprintf(f, "\n");
10686             fprintf(f, "%s", commentList[i]);
10687             linelen = 0;
10688             newblock = TRUE;
10689         }
10690
10691         /* Format move number */
10692         if ((i % 2) == 0) {
10693             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10694         } else {
10695             if (newblock) {
10696                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10697             } else {
10698                 numtext[0] = NULLCHAR;
10699             }
10700         }
10701         numlen = strlen(numtext);
10702         newblock = FALSE;
10703
10704         /* Print move number */
10705         blank = linelen > 0 && numlen > 0;
10706         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10707             fprintf(f, "\n");
10708             linelen = 0;
10709             blank = 0;
10710         }
10711         if (blank) {
10712             fprintf(f, " ");
10713             linelen++;
10714         }
10715         fprintf(f, "%s", numtext);
10716         linelen += numlen;
10717
10718         /* Get move */
10719         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10720         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10721
10722         /* Print move */
10723         blank = linelen > 0 && movelen > 0;
10724         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10725             fprintf(f, "\n");
10726             linelen = 0;
10727             blank = 0;
10728         }
10729         if (blank) {
10730             fprintf(f, " ");
10731             linelen++;
10732         }
10733         fprintf(f, "%s", move_buffer);
10734         linelen += movelen;
10735
10736         /* [AS] Add PV info if present */
10737         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10738             /* [HGM] add time */
10739             char buf[MSG_SIZ]; int seconds;
10740
10741             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10742
10743             if( seconds <= 0) buf[0] = 0; else
10744             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10745                 seconds = (seconds + 4)/10; // round to full seconds
10746                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10747                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10748             }
10749
10750             sprintf( move_buffer, "{%s%.2f/%d%s}",
10751                 pvInfoList[i].score >= 0 ? "+" : "",
10752                 pvInfoList[i].score / 100.0,
10753                 pvInfoList[i].depth,
10754                 buf );
10755
10756             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10757
10758             /* Print score/depth */
10759             blank = linelen > 0 && movelen > 0;
10760             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10761                 fprintf(f, "\n");
10762                 linelen = 0;
10763                 blank = 0;
10764             }
10765             if (blank) {
10766                 fprintf(f, " ");
10767                 linelen++;
10768             }
10769             fprintf(f, "%s", move_buffer);
10770             linelen += movelen;
10771         }
10772
10773         i++;
10774     }
10775
10776     /* Start a new line */
10777     if (linelen > 0) fprintf(f, "\n");
10778
10779     /* Print comments after last move */
10780     if (commentList[i] != NULL) {
10781         fprintf(f, "%s\n", commentList[i]);
10782     }
10783
10784     /* Print result */
10785     if (gameInfo.resultDetails != NULL &&
10786         gameInfo.resultDetails[0] != NULLCHAR) {
10787         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10788                 PGNResult(gameInfo.result));
10789     } else {
10790         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10791     }
10792
10793     fclose(f);
10794     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10795     return TRUE;
10796 }
10797
10798 /* Save game in old style and close the file */
10799 int
10800 SaveGameOldStyle(f)
10801      FILE *f;
10802 {
10803     int i, offset;
10804     time_t tm;
10805
10806     tm = time((time_t *) NULL);
10807
10808     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10809     PrintOpponents(f);
10810
10811     if (backwardMostMove > 0 || startedFromSetupPosition) {
10812         fprintf(f, "\n[--------------\n");
10813         PrintPosition(f, backwardMostMove);
10814         fprintf(f, "--------------]\n");
10815     } else {
10816         fprintf(f, "\n");
10817     }
10818
10819     i = backwardMostMove;
10820     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10821
10822     while (i < forwardMostMove) {
10823         if (commentList[i] != NULL) {
10824             fprintf(f, "[%s]\n", commentList[i]);
10825         }
10826
10827         if ((i % 2) == 1) {
10828             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10829             i++;
10830         } else {
10831             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10832             i++;
10833             if (commentList[i] != NULL) {
10834                 fprintf(f, "\n");
10835                 continue;
10836             }
10837             if (i >= forwardMostMove) {
10838                 fprintf(f, "\n");
10839                 break;
10840             }
10841             fprintf(f, "%s\n", parseList[i]);
10842             i++;
10843         }
10844     }
10845
10846     if (commentList[i] != NULL) {
10847         fprintf(f, "[%s]\n", commentList[i]);
10848     }
10849
10850     /* This isn't really the old style, but it's close enough */
10851     if (gameInfo.resultDetails != NULL &&
10852         gameInfo.resultDetails[0] != NULLCHAR) {
10853         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10854                 gameInfo.resultDetails);
10855     } else {
10856         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10857     }
10858
10859     fclose(f);
10860     return TRUE;
10861 }
10862
10863 /* Save the current game to open file f and close the file */
10864 int
10865 SaveGame(f, dummy, dummy2)
10866      FILE *f;
10867      int dummy;
10868      char *dummy2;
10869 {
10870     if (gameMode == EditPosition) EditPositionDone(TRUE);
10871     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10872     if (appData.oldSaveStyle)
10873       return SaveGameOldStyle(f);
10874     else
10875       return SaveGamePGN(f);
10876 }
10877
10878 /* Save the current position to the given file */
10879 int
10880 SavePositionToFile(filename)
10881      char *filename;
10882 {
10883     FILE *f;
10884     char buf[MSG_SIZ];
10885
10886     if (strcmp(filename, "-") == 0) {
10887         return SavePosition(stdout, 0, NULL);
10888     } else {
10889         f = fopen(filename, "a");
10890         if (f == NULL) {
10891             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10892             DisplayError(buf, errno);
10893             return FALSE;
10894         } else {
10895             SavePosition(f, 0, NULL);
10896             return TRUE;
10897         }
10898     }
10899 }
10900
10901 /* Save the current position to the given open file and close the file */
10902 int
10903 SavePosition(f, dummy, dummy2)
10904      FILE *f;
10905      int dummy;
10906      char *dummy2;
10907 {
10908     time_t tm;
10909     char *fen;
10910     if (gameMode == EditPosition) EditPositionDone(TRUE);
10911     if (appData.oldSaveStyle) {
10912         tm = time((time_t *) NULL);
10913
10914         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10915         PrintOpponents(f);
10916         fprintf(f, "[--------------\n");
10917         PrintPosition(f, currentMove);
10918         fprintf(f, "--------------]\n");
10919     } else {
10920         fen = PositionToFEN(currentMove, NULL);
10921         fprintf(f, "%s\n", fen);
10922         free(fen);
10923     }
10924     fclose(f);
10925     return TRUE;
10926 }
10927
10928 void
10929 ReloadCmailMsgEvent(unregister)
10930      int unregister;
10931 {
10932 #if !WIN32
10933     static char *inFilename = NULL;
10934     static char *outFilename;
10935     int i;
10936     struct stat inbuf, outbuf;
10937     int status;
10938
10939     /* Any registered moves are unregistered if unregister is set, */
10940     /* i.e. invoked by the signal handler */
10941     if (unregister) {
10942         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10943             cmailMoveRegistered[i] = FALSE;
10944             if (cmailCommentList[i] != NULL) {
10945                 free(cmailCommentList[i]);
10946                 cmailCommentList[i] = NULL;
10947             }
10948         }
10949         nCmailMovesRegistered = 0;
10950     }
10951
10952     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10953         cmailResult[i] = CMAIL_NOT_RESULT;
10954     }
10955     nCmailResults = 0;
10956
10957     if (inFilename == NULL) {
10958         /* Because the filenames are static they only get malloced once  */
10959         /* and they never get freed                                      */
10960         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10961         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10962
10963         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10964         sprintf(outFilename, "%s.out", appData.cmailGameName);
10965     }
10966
10967     status = stat(outFilename, &outbuf);
10968     if (status < 0) {
10969         cmailMailedMove = FALSE;
10970     } else {
10971         status = stat(inFilename, &inbuf);
10972         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10973     }
10974
10975     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10976        counts the games, notes how each one terminated, etc.
10977
10978        It would be nice to remove this kludge and instead gather all
10979        the information while building the game list.  (And to keep it
10980        in the game list nodes instead of having a bunch of fixed-size
10981        parallel arrays.)  Note this will require getting each game's
10982        termination from the PGN tags, as the game list builder does
10983        not process the game moves.  --mann
10984        */
10985     cmailMsgLoaded = TRUE;
10986     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10987
10988     /* Load first game in the file or popup game menu */
10989     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10990
10991 #endif /* !WIN32 */
10992     return;
10993 }
10994
10995 int
10996 RegisterMove()
10997 {
10998     FILE *f;
10999     char string[MSG_SIZ];
11000
11001     if (   cmailMailedMove
11002         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11003         return TRUE;            /* Allow free viewing  */
11004     }
11005
11006     /* Unregister move to ensure that we don't leave RegisterMove        */
11007     /* with the move registered when the conditions for registering no   */
11008     /* longer hold                                                       */
11009     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11010         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11011         nCmailMovesRegistered --;
11012
11013         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11014           {
11015               free(cmailCommentList[lastLoadGameNumber - 1]);
11016               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11017           }
11018     }
11019
11020     if (cmailOldMove == -1) {
11021         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11022         return FALSE;
11023     }
11024
11025     if (currentMove > cmailOldMove + 1) {
11026         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11027         return FALSE;
11028     }
11029
11030     if (currentMove < cmailOldMove) {
11031         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11032         return FALSE;
11033     }
11034
11035     if (forwardMostMove > currentMove) {
11036         /* Silently truncate extra moves */
11037         TruncateGame();
11038     }
11039
11040     if (   (currentMove == cmailOldMove + 1)
11041         || (   (currentMove == cmailOldMove)
11042             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11043                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11044         if (gameInfo.result != GameUnfinished) {
11045             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11046         }
11047
11048         if (commentList[currentMove] != NULL) {
11049             cmailCommentList[lastLoadGameNumber - 1]
11050               = StrSave(commentList[currentMove]);
11051         }
11052         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11053
11054         if (appData.debugMode)
11055           fprintf(debugFP, "Saving %s for game %d\n",
11056                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11057
11058         sprintf(string,
11059                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11060
11061         f = fopen(string, "w");
11062         if (appData.oldSaveStyle) {
11063             SaveGameOldStyle(f); /* also closes the file */
11064
11065             sprintf(string, "%s.pos.out", appData.cmailGameName);
11066             f = fopen(string, "w");
11067             SavePosition(f, 0, NULL); /* also closes the file */
11068         } else {
11069             fprintf(f, "{--------------\n");
11070             PrintPosition(f, currentMove);
11071             fprintf(f, "--------------}\n\n");
11072
11073             SaveGame(f, 0, NULL); /* also closes the file*/
11074         }
11075
11076         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11077         nCmailMovesRegistered ++;
11078     } else if (nCmailGames == 1) {
11079         DisplayError(_("You have not made a move yet"), 0);
11080         return FALSE;
11081     }
11082
11083     return TRUE;
11084 }
11085
11086 void
11087 MailMoveEvent()
11088 {
11089 #if !WIN32
11090     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11091     FILE *commandOutput;
11092     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11093     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11094     int nBuffers;
11095     int i;
11096     int archived;
11097     char *arcDir;
11098
11099     if (! cmailMsgLoaded) {
11100         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11101         return;
11102     }
11103
11104     if (nCmailGames == nCmailResults) {
11105         DisplayError(_("No unfinished games"), 0);
11106         return;
11107     }
11108
11109 #if CMAIL_PROHIBIT_REMAIL
11110     if (cmailMailedMove) {
11111         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);
11112         DisplayError(msg, 0);
11113         return;
11114     }
11115 #endif
11116
11117     if (! (cmailMailedMove || RegisterMove())) return;
11118
11119     if (   cmailMailedMove
11120         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11121         sprintf(string, partCommandString,
11122                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11123         commandOutput = popen(string, "r");
11124
11125         if (commandOutput == NULL) {
11126             DisplayError(_("Failed to invoke cmail"), 0);
11127         } else {
11128             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11129                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11130             }
11131             if (nBuffers > 1) {
11132                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11133                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11134                 nBytes = MSG_SIZ - 1;
11135             } else {
11136                 (void) memcpy(msg, buffer, nBytes);
11137             }
11138             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11139
11140             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11141                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11142
11143                 archived = TRUE;
11144                 for (i = 0; i < nCmailGames; i ++) {
11145                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11146                         archived = FALSE;
11147                     }
11148                 }
11149                 if (   archived
11150                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11151                         != NULL)) {
11152                     sprintf(buffer, "%s/%s.%s.archive",
11153                             arcDir,
11154                             appData.cmailGameName,
11155                             gameInfo.date);
11156                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11157                     cmailMsgLoaded = FALSE;
11158                 }
11159             }
11160
11161             DisplayInformation(msg);
11162             pclose(commandOutput);
11163         }
11164     } else {
11165         if ((*cmailMsg) != '\0') {
11166             DisplayInformation(cmailMsg);
11167         }
11168     }
11169
11170     return;
11171 #endif /* !WIN32 */
11172 }
11173
11174 char *
11175 CmailMsg()
11176 {
11177 #if WIN32
11178     return NULL;
11179 #else
11180     int  prependComma = 0;
11181     char number[5];
11182     char string[MSG_SIZ];       /* Space for game-list */
11183     int  i;
11184
11185     if (!cmailMsgLoaded) return "";
11186
11187     if (cmailMailedMove) {
11188         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11189     } else {
11190         /* Create a list of games left */
11191         sprintf(string, "[");
11192         for (i = 0; i < nCmailGames; i ++) {
11193             if (! (   cmailMoveRegistered[i]
11194                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11195                 if (prependComma) {
11196                     sprintf(number, ",%d", i + 1);
11197                 } else {
11198                     sprintf(number, "%d", i + 1);
11199                     prependComma = 1;
11200                 }
11201
11202                 strcat(string, number);
11203             }
11204         }
11205         strcat(string, "]");
11206
11207         if (nCmailMovesRegistered + nCmailResults == 0) {
11208             switch (nCmailGames) {
11209               case 1:
11210                 sprintf(cmailMsg,
11211                         _("Still need to make move for game\n"));
11212                 break;
11213
11214               case 2:
11215                 sprintf(cmailMsg,
11216                         _("Still need to make moves for both games\n"));
11217                 break;
11218
11219               default:
11220                 sprintf(cmailMsg,
11221                         _("Still need to make moves for all %d games\n"),
11222                         nCmailGames);
11223                 break;
11224             }
11225         } else {
11226             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11227               case 1:
11228                 sprintf(cmailMsg,
11229                         _("Still need to make a move for game %s\n"),
11230                         string);
11231                 break;
11232
11233               case 0:
11234                 if (nCmailResults == nCmailGames) {
11235                     sprintf(cmailMsg, _("No unfinished games\n"));
11236                 } else {
11237                     sprintf(cmailMsg, _("Ready to send mail\n"));
11238                 }
11239                 break;
11240
11241               default:
11242                 sprintf(cmailMsg,
11243                         _("Still need to make moves for games %s\n"),
11244                         string);
11245             }
11246         }
11247     }
11248     return cmailMsg;
11249 #endif /* WIN32 */
11250 }
11251
11252 void
11253 ResetGameEvent()
11254 {
11255     if (gameMode == Training)
11256       SetTrainingModeOff();
11257
11258     Reset(TRUE, TRUE);
11259     cmailMsgLoaded = FALSE;
11260     if (appData.icsActive) {
11261       SendToICS(ics_prefix);
11262       SendToICS("refresh\n");
11263     }
11264 }
11265
11266 void
11267 ExitEvent(status)
11268      int status;
11269 {
11270     exiting++;
11271     if (exiting > 2) {
11272       /* Give up on clean exit */
11273       exit(status);
11274     }
11275     if (exiting > 1) {
11276       /* Keep trying for clean exit */
11277       return;
11278     }
11279
11280     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11281
11282     if (telnetISR != NULL) {
11283       RemoveInputSource(telnetISR);
11284     }
11285     if (icsPR != NoProc) {
11286       DestroyChildProcess(icsPR, TRUE);
11287     }
11288
11289     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11290     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11291
11292     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11293     /* make sure this other one finishes before killing it!                  */
11294     if(endingGame) { int count = 0;
11295         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11296         while(endingGame && count++ < 10) DoSleep(1);
11297         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11298     }
11299
11300     /* Kill off chess programs */
11301     if (first.pr != NoProc) {
11302         ExitAnalyzeMode();
11303
11304         DoSleep( appData.delayBeforeQuit );
11305         SendToProgram("quit\n", &first);
11306         DoSleep( appData.delayAfterQuit );
11307         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11308     }
11309     if (second.pr != NoProc) {
11310         DoSleep( appData.delayBeforeQuit );
11311         SendToProgram("quit\n", &second);
11312         DoSleep( appData.delayAfterQuit );
11313         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11314     }
11315     if (first.isr != NULL) {
11316         RemoveInputSource(first.isr);
11317     }
11318     if (second.isr != NULL) {
11319         RemoveInputSource(second.isr);
11320     }
11321
11322     ShutDownFrontEnd();
11323     exit(status);
11324 }
11325
11326 void
11327 PauseEvent()
11328 {
11329     if (appData.debugMode)
11330         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11331     if (pausing) {
11332         pausing = FALSE;
11333         ModeHighlight();
11334         if (gameMode == MachinePlaysWhite ||
11335             gameMode == MachinePlaysBlack) {
11336             StartClocks();
11337         } else {
11338             DisplayBothClocks();
11339         }
11340         if (gameMode == PlayFromGameFile) {
11341             if (appData.timeDelay >= 0)
11342                 AutoPlayGameLoop();
11343         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11344             Reset(FALSE, TRUE);
11345             SendToICS(ics_prefix);
11346             SendToICS("refresh\n");
11347         } else if (currentMove < forwardMostMove) {
11348             ForwardInner(forwardMostMove);
11349         }
11350         pauseExamInvalid = FALSE;
11351     } else {
11352         switch (gameMode) {
11353           default:
11354             return;
11355           case IcsExamining:
11356             pauseExamForwardMostMove = forwardMostMove;
11357             pauseExamInvalid = FALSE;
11358             /* fall through */
11359           case IcsObserving:
11360           case IcsPlayingWhite:
11361           case IcsPlayingBlack:
11362             pausing = TRUE;
11363             ModeHighlight();
11364             return;
11365           case PlayFromGameFile:
11366             (void) StopLoadGameTimer();
11367             pausing = TRUE;
11368             ModeHighlight();
11369             break;
11370           case BeginningOfGame:
11371             if (appData.icsActive) return;
11372             /* else fall through */
11373           case MachinePlaysWhite:
11374           case MachinePlaysBlack:
11375           case TwoMachinesPlay:
11376             if (forwardMostMove == 0)
11377               return;           /* don't pause if no one has moved */
11378             if ((gameMode == MachinePlaysWhite &&
11379                  !WhiteOnMove(forwardMostMove)) ||
11380                 (gameMode == MachinePlaysBlack &&
11381                  WhiteOnMove(forwardMostMove))) {
11382                 StopClocks();
11383             }
11384             pausing = TRUE;
11385             ModeHighlight();
11386             break;
11387         }
11388     }
11389 }
11390
11391 void
11392 EditCommentEvent()
11393 {
11394     char title[MSG_SIZ];
11395
11396     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11397         strcpy(title, _("Edit comment"));
11398     } else {
11399         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11400                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11401                 parseList[currentMove - 1]);
11402     }
11403
11404     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11405 }
11406
11407
11408 void
11409 EditTagsEvent()
11410 {
11411     char *tags = PGNTags(&gameInfo);
11412     EditTagsPopUp(tags);
11413     free(tags);
11414 }
11415
11416 void
11417 AnalyzeModeEvent()
11418 {
11419     if (appData.noChessProgram || gameMode == AnalyzeMode)
11420       return;
11421
11422     if (gameMode != AnalyzeFile) {
11423         if (!appData.icsEngineAnalyze) {
11424                EditGameEvent();
11425                if (gameMode != EditGame) return;
11426         }
11427         ResurrectChessProgram();
11428         SendToProgram("analyze\n", &first);
11429         first.analyzing = TRUE;
11430         /*first.maybeThinking = TRUE;*/
11431         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11432         EngineOutputPopUp();
11433     }
11434     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11435     pausing = FALSE;
11436     ModeHighlight();
11437     SetGameInfo();
11438
11439     StartAnalysisClock();
11440     GetTimeMark(&lastNodeCountTime);
11441     lastNodeCount = 0;
11442 }
11443
11444 void
11445 AnalyzeFileEvent()
11446 {
11447     if (appData.noChessProgram || gameMode == AnalyzeFile)
11448       return;
11449
11450     if (gameMode != AnalyzeMode) {
11451         EditGameEvent();
11452         if (gameMode != EditGame) return;
11453         ResurrectChessProgram();
11454         SendToProgram("analyze\n", &first);
11455         first.analyzing = TRUE;
11456         /*first.maybeThinking = TRUE;*/
11457         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11458         EngineOutputPopUp();
11459     }
11460     gameMode = AnalyzeFile;
11461     pausing = FALSE;
11462     ModeHighlight();
11463     SetGameInfo();
11464
11465     StartAnalysisClock();
11466     GetTimeMark(&lastNodeCountTime);
11467     lastNodeCount = 0;
11468 }
11469
11470 void
11471 MachineWhiteEvent()
11472 {
11473     char buf[MSG_SIZ];
11474     char *bookHit = NULL;
11475
11476     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11477       return;
11478
11479
11480     if (gameMode == PlayFromGameFile ||
11481         gameMode == TwoMachinesPlay  ||
11482         gameMode == Training         ||
11483         gameMode == AnalyzeMode      ||
11484         gameMode == EndOfGame)
11485         EditGameEvent();
11486
11487     if (gameMode == EditPosition) 
11488         EditPositionDone(TRUE);
11489
11490     if (!WhiteOnMove(currentMove)) {
11491         DisplayError(_("It is not White's turn"), 0);
11492         return;
11493     }
11494
11495     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11496       ExitAnalyzeMode();
11497
11498     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11499         gameMode == AnalyzeFile)
11500         TruncateGame();
11501
11502     ResurrectChessProgram();    /* in case it isn't running */
11503     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11504         gameMode = MachinePlaysWhite;
11505         ResetClocks();
11506     } else
11507     gameMode = MachinePlaysWhite;
11508     pausing = FALSE;
11509     ModeHighlight();
11510     SetGameInfo();
11511     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11512     DisplayTitle(buf);
11513     if (first.sendName) {
11514       sprintf(buf, "name %s\n", gameInfo.black);
11515       SendToProgram(buf, &first);
11516     }
11517     if (first.sendTime) {
11518       if (first.useColors) {
11519         SendToProgram("black\n", &first); /*gnu kludge*/
11520       }
11521       SendTimeRemaining(&first, TRUE);
11522     }
11523     if (first.useColors) {
11524       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11525     }
11526     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11527     SetMachineThinkingEnables();
11528     first.maybeThinking = TRUE;
11529     StartClocks();
11530     firstMove = FALSE;
11531
11532     if (appData.autoFlipView && !flipView) {
11533       flipView = !flipView;
11534       DrawPosition(FALSE, NULL);
11535       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11536     }
11537
11538     if(bookHit) { // [HGM] book: simulate book reply
11539         static char bookMove[MSG_SIZ]; // a bit generous?
11540
11541         programStats.nodes = programStats.depth = programStats.time =
11542         programStats.score = programStats.got_only_move = 0;
11543         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11544
11545         strcpy(bookMove, "move ");
11546         strcat(bookMove, bookHit);
11547         HandleMachineMove(bookMove, &first);
11548     }
11549 }
11550
11551 void
11552 MachineBlackEvent()
11553 {
11554   char buf[MSG_SIZ];
11555   char *bookHit = NULL;
11556   
11557   if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11558     return;
11559   
11560   
11561   if (gameMode == PlayFromGameFile 
11562       || gameMode == TwoMachinesPlay  
11563       || gameMode == Training     
11564       || gameMode == AnalyzeMode
11565       || gameMode == EndOfGame)
11566     EditGameEvent();
11567   
11568   if (gameMode == EditPosition) 
11569     EditPositionDone(TRUE);
11570   
11571   if (WhiteOnMove(currentMove)) 
11572     {
11573       DisplayError(_("It is not Black's turn"), 0);
11574       return;
11575     }
11576   
11577   if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11578     ExitAnalyzeMode();
11579   
11580   if (gameMode == EditGame || gameMode == AnalyzeMode 
11581       || gameMode == AnalyzeFile)
11582     TruncateGame();
11583   
11584   ResurrectChessProgram();      /* in case it isn't running */
11585   gameMode = MachinePlaysBlack;
11586   pausing  = FALSE;
11587   ModeHighlight();
11588   SetGameInfo();
11589   sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11590   DisplayTitle(buf);
11591   if (first.sendName) 
11592     {
11593       sprintf(buf, "name %s\n", gameInfo.white);
11594       SendToProgram(buf, &first);
11595     }
11596   if (first.sendTime) 
11597     {
11598       if (first.useColors) 
11599         {
11600           SendToProgram("white\n", &first); /*gnu kludge*/
11601         }
11602       SendTimeRemaining(&first, FALSE);
11603     }
11604   if (first.useColors) 
11605     {
11606       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11607     }
11608   bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11609   SetMachineThinkingEnables();
11610   first.maybeThinking = TRUE;
11611   StartClocks();
11612   
11613   if (appData.autoFlipView && flipView) 
11614     {
11615       flipView = !flipView;
11616       DrawPosition(FALSE, NULL);
11617       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11618     }
11619   if(bookHit) 
11620     { // [HGM] book: simulate book reply
11621       static char bookMove[MSG_SIZ]; // a bit generous?
11622       
11623       programStats.nodes = programStats.depth = programStats.time 
11624         = programStats.score = programStats.got_only_move = 0;
11625       sprintf(programStats.movelist, "%s (xbook)", bookHit);
11626       
11627       strcpy(bookMove, "move ");
11628       strcat(bookMove, bookHit);
11629       HandleMachineMove(bookMove, &first);
11630     }
11631   return;
11632 }
11633
11634
11635 void
11636 DisplayTwoMachinesTitle()
11637 {
11638     char buf[MSG_SIZ];
11639     if (appData.matchGames > 0) {
11640         if (first.twoMachinesColor[0] == 'w') {
11641             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11642                     gameInfo.white, gameInfo.black,
11643                     first.matchWins, second.matchWins,
11644                     matchGame - 1 - (first.matchWins + second.matchWins));
11645         } else {
11646             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11647                     gameInfo.white, gameInfo.black,
11648                     second.matchWins, first.matchWins,
11649                     matchGame - 1 - (first.matchWins + second.matchWins));
11650         }
11651     } else {
11652         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11653     }
11654     DisplayTitle(buf);
11655 }
11656
11657 void
11658 TwoMachinesEvent P((void))
11659 {
11660     int i;
11661     char buf[MSG_SIZ];
11662     ChessProgramState *onmove;
11663     char *bookHit = NULL;
11664
11665     if (appData.noChessProgram) return;
11666
11667     switch (gameMode) {
11668       case TwoMachinesPlay:
11669         return;
11670       case MachinePlaysWhite:
11671       case MachinePlaysBlack:
11672         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11673             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11674             return;
11675         }
11676         /* fall through */
11677       case BeginningOfGame:
11678       case PlayFromGameFile:
11679       case EndOfGame:
11680         EditGameEvent();
11681         if (gameMode != EditGame) return;
11682         break;
11683       case EditPosition:
11684         EditPositionDone(TRUE);
11685         break;
11686       case AnalyzeMode:
11687       case AnalyzeFile:
11688         ExitAnalyzeMode();
11689         break;
11690       case EditGame:
11691       default:
11692         break;
11693     }
11694
11695 //    forwardMostMove = currentMove;
11696     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11697     ResurrectChessProgram();    /* in case first program isn't running */
11698
11699     if (second.pr == NULL) {
11700         StartChessProgram(&second);
11701         if (second.protocolVersion == 1) {
11702           TwoMachinesEventIfReady();
11703         } else {
11704           /* kludge: allow timeout for initial "feature" command */
11705           FreezeUI();
11706           DisplayMessage("", _("Starting second chess program"));
11707           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11708         }
11709         return;
11710     }
11711     DisplayMessage("", "");
11712     InitChessProgram(&second, FALSE);
11713     SendToProgram("force\n", &second);
11714     if (startedFromSetupPosition) {
11715         SendBoard(&second, backwardMostMove);
11716     if (appData.debugMode) {
11717         fprintf(debugFP, "Two Machines\n");
11718     }
11719     }
11720     for (i = backwardMostMove; i < forwardMostMove; i++) {
11721         SendMoveToProgram(i, &second);
11722     }
11723
11724     gameMode = TwoMachinesPlay;
11725     pausing = FALSE;
11726     ModeHighlight();
11727     SetGameInfo();
11728     DisplayTwoMachinesTitle();
11729     firstMove = TRUE;
11730     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11731         onmove = &first;
11732     } else {
11733         onmove = &second;
11734     }
11735
11736     SendToProgram(first.computerString, &first);
11737     if (first.sendName) {
11738       sprintf(buf, "name %s\n", second.tidy);
11739       SendToProgram(buf, &first);
11740     }
11741     SendToProgram(second.computerString, &second);
11742     if (second.sendName) {
11743       sprintf(buf, "name %s\n", first.tidy);
11744       SendToProgram(buf, &second);
11745     }
11746
11747     ResetClocks();
11748     if (!first.sendTime || !second.sendTime) {
11749         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11750         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11751     }
11752     if (onmove->sendTime) {
11753       if (onmove->useColors) {
11754         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11755       }
11756       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11757     }
11758     if (onmove->useColors) {
11759       SendToProgram(onmove->twoMachinesColor, onmove);
11760     }
11761     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11762 //    SendToProgram("go\n", onmove);
11763     onmove->maybeThinking = TRUE;
11764     SetMachineThinkingEnables();
11765
11766     StartClocks();
11767
11768     if(bookHit) { // [HGM] book: simulate book reply
11769         static char bookMove[MSG_SIZ]; // a bit generous?
11770
11771         programStats.nodes = programStats.depth = programStats.time =
11772         programStats.score = programStats.got_only_move = 0;
11773         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11774
11775         strcpy(bookMove, "move ");
11776         strcat(bookMove, bookHit);
11777         savedMessage = bookMove; // args for deferred call
11778         savedState = onmove;
11779         ScheduleDelayedEvent(DeferredBookMove, 1);
11780     }
11781 }
11782
11783 void
11784 TrainingEvent()
11785 {
11786     if (gameMode == Training) {
11787       SetTrainingModeOff();
11788       gameMode = PlayFromGameFile;
11789       DisplayMessage("", _("Training mode off"));
11790     } else {
11791       gameMode = Training;
11792       animateTraining = appData.animate;
11793
11794       /* make sure we are not already at the end of the game */
11795       if (currentMove < forwardMostMove) {
11796         SetTrainingModeOn();
11797         DisplayMessage("", _("Training mode on"));
11798       } else {
11799         gameMode = PlayFromGameFile;
11800         DisplayError(_("Already at end of game"), 0);
11801       }
11802     }
11803     ModeHighlight();
11804 }
11805
11806 void
11807 IcsClientEvent()
11808 {
11809     if (!appData.icsActive) return;
11810     switch (gameMode) {
11811       case IcsPlayingWhite:
11812       case IcsPlayingBlack:
11813       case IcsObserving:
11814       case IcsIdle:
11815       case BeginningOfGame:
11816       case IcsExamining:
11817         return;
11818
11819       case EditGame:
11820         break;
11821
11822       case EditPosition:
11823         EditPositionDone(TRUE);
11824         break;
11825
11826       case AnalyzeMode:
11827       case AnalyzeFile:
11828         ExitAnalyzeMode();
11829         break;
11830
11831       default:
11832         EditGameEvent();
11833         break;
11834     }
11835
11836     gameMode = IcsIdle;
11837     ModeHighlight();
11838     return;
11839 }
11840
11841
11842 void
11843 EditGameEvent()
11844 {
11845     int i;
11846
11847     switch (gameMode) {
11848       case Training:
11849         SetTrainingModeOff();
11850         break;
11851       case MachinePlaysWhite:
11852       case MachinePlaysBlack:
11853       case BeginningOfGame:
11854         SendToProgram("force\n", &first);
11855         SetUserThinkingEnables();
11856         break;
11857       case PlayFromGameFile:
11858         (void) StopLoadGameTimer();
11859         if (gameFileFP != NULL) {
11860             gameFileFP = NULL;
11861         }
11862         break;
11863       case EditPosition:
11864         EditPositionDone(TRUE);
11865         break;
11866       case AnalyzeMode:
11867       case AnalyzeFile:
11868         ExitAnalyzeMode();
11869         SendToProgram("force\n", &first);
11870         break;
11871       case TwoMachinesPlay:
11872         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11873         ResurrectChessProgram();
11874         SetUserThinkingEnables();
11875         break;
11876       case EndOfGame:
11877         ResurrectChessProgram();
11878         break;
11879       case IcsPlayingBlack:
11880       case IcsPlayingWhite:
11881         DisplayError(_("Warning: You are still playing a game"), 0);
11882         break;
11883       case IcsObserving:
11884         DisplayError(_("Warning: You are still observing a game"), 0);
11885         break;
11886       case IcsExamining:
11887         DisplayError(_("Warning: You are still examining a game"), 0);
11888         break;
11889       case IcsIdle:
11890         break;
11891       case EditGame:
11892       default:
11893         return;
11894     }
11895
11896     pausing = FALSE;
11897     StopClocks();
11898     first.offeredDraw = second.offeredDraw = 0;
11899
11900     if (gameMode == PlayFromGameFile) {
11901         whiteTimeRemaining = timeRemaining[0][currentMove];
11902         blackTimeRemaining = timeRemaining[1][currentMove];
11903         DisplayTitle("");
11904     }
11905
11906     if (gameMode == MachinePlaysWhite ||
11907         gameMode == MachinePlaysBlack ||
11908         gameMode == TwoMachinesPlay ||
11909         gameMode == EndOfGame) {
11910         i = forwardMostMove;
11911         while (i > currentMove) {
11912             SendToProgram("undo\n", &first);
11913             i--;
11914         }
11915         whiteTimeRemaining = timeRemaining[0][currentMove];
11916         blackTimeRemaining = timeRemaining[1][currentMove];
11917         DisplayBothClocks();
11918         if (whiteFlag || blackFlag) {
11919             whiteFlag = blackFlag = 0;
11920         }
11921         DisplayTitle("");
11922     }
11923
11924     gameMode = EditGame;
11925     ModeHighlight();
11926     SetGameInfo();
11927 }
11928
11929
11930 void
11931 EditPositionEvent()
11932 {
11933     if (gameMode == EditPosition) {
11934         EditGameEvent();
11935         return;
11936     }
11937
11938     EditGameEvent();
11939     if (gameMode != EditGame) return;
11940
11941     gameMode = EditPosition;
11942     ModeHighlight();
11943     SetGameInfo();
11944     if (currentMove > 0)
11945       CopyBoard(boards[0], boards[currentMove]);
11946
11947     blackPlaysFirst = !WhiteOnMove(currentMove);
11948     ResetClocks();
11949     currentMove = forwardMostMove = backwardMostMove = 0;
11950     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11951     DisplayMove(-1);
11952 }
11953
11954 void
11955 ExitAnalyzeMode()
11956 {
11957     /* [DM] icsEngineAnalyze - possible call from other functions */
11958     if (appData.icsEngineAnalyze) {
11959         appData.icsEngineAnalyze = FALSE;
11960
11961         DisplayMessage("",_("Close ICS engine analyze..."));
11962     }
11963     if (first.analysisSupport && first.analyzing) {
11964       SendToProgram("exit\n", &first);
11965       first.analyzing = FALSE;
11966     }
11967     thinkOutput[0] = NULLCHAR;
11968 }
11969
11970 void
11971 EditPositionDone(Boolean fakeRights)
11972 {
11973     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11974
11975     startedFromSetupPosition = TRUE;
11976     InitChessProgram(&first, FALSE);
11977     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11978       boards[0][EP_STATUS] = EP_NONE;
11979       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11980     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11981         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11982         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11983       } else boards[0][CASTLING][2] = NoRights;
11984     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11985         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11986         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11987       } else boards[0][CASTLING][5] = NoRights;
11988     }
11989     SendToProgram("force\n", &first);
11990     if (blackPlaysFirst) {
11991         strcpy(moveList[0], "");
11992         strcpy(parseList[0], "");
11993         currentMove = forwardMostMove = backwardMostMove = 1;
11994         CopyBoard(boards[1], boards[0]);
11995     } else {
11996         currentMove = forwardMostMove = backwardMostMove = 0;
11997     }
11998     SendBoard(&first, forwardMostMove);
11999     if (appData.debugMode) {
12000         fprintf(debugFP, "EditPosDone\n");
12001     }
12002     DisplayTitle("");
12003     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12004     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12005     gameMode = EditGame;
12006     ModeHighlight();
12007     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12008     ClearHighlights(); /* [AS] */
12009 }
12010
12011 /* Pause for `ms' milliseconds */
12012 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12013 void
12014 TimeDelay(ms)
12015      long ms;
12016 {
12017     TimeMark m1, m2;
12018
12019     GetTimeMark(&m1);
12020     do {
12021         GetTimeMark(&m2);
12022     } while (SubtractTimeMarks(&m2, &m1) < ms);
12023 }
12024
12025 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12026 void
12027 SendMultiLineToICS(buf)
12028      char *buf;
12029 {
12030     char temp[MSG_SIZ+1], *p;
12031     int len;
12032
12033     len = strlen(buf);
12034     if (len > MSG_SIZ)
12035       len = MSG_SIZ;
12036
12037     strncpy(temp, buf, len);
12038     temp[len] = 0;
12039
12040     p = temp;
12041     while (*p) {
12042         if (*p == '\n' || *p == '\r')
12043           *p = ' ';
12044         ++p;
12045     }
12046
12047     strcat(temp, "\n");
12048     SendToICS(temp);
12049     SendToPlayer(temp, strlen(temp));
12050 }
12051
12052 void
12053 SetWhiteToPlayEvent()
12054 {
12055     if (gameMode == EditPosition) {
12056         blackPlaysFirst = FALSE;
12057         DisplayBothClocks();    /* works because currentMove is 0 */
12058     } else if (gameMode == IcsExamining) {
12059         SendToICS(ics_prefix);
12060         SendToICS("tomove white\n");
12061     }
12062 }
12063
12064 void
12065 SetBlackToPlayEvent()
12066 {
12067     if (gameMode == EditPosition) {
12068         blackPlaysFirst = TRUE;
12069         currentMove = 1;        /* kludge */
12070         DisplayBothClocks();
12071         currentMove = 0;
12072     } else if (gameMode == IcsExamining) {
12073         SendToICS(ics_prefix);
12074         SendToICS("tomove black\n");
12075     }
12076 }
12077
12078 void
12079 EditPositionMenuEvent(selection, x, y)
12080      ChessSquare selection;
12081      int x, y;
12082 {
12083     char buf[MSG_SIZ];
12084     ChessSquare piece = boards[0][y][x];
12085
12086     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12087
12088     switch (selection) {
12089       case ClearBoard:
12090         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12091             SendToICS(ics_prefix);
12092             SendToICS("bsetup clear\n");
12093         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12094             SendToICS(ics_prefix);
12095             SendToICS("clearboard\n");
12096         } else {
12097             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12098                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12099                 for (y = 0; y < BOARD_HEIGHT; y++) {
12100                     if (gameMode == IcsExamining) {
12101                         if (boards[currentMove][y][x] != EmptySquare) {
12102                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12103                                     AAA + x, ONE + y);
12104                             SendToICS(buf);
12105                         }
12106                     } else {
12107                         boards[0][y][x] = p;
12108                     }
12109                 }
12110             }
12111         }
12112         if (gameMode == EditPosition) {
12113             DrawPosition(FALSE, boards[0]);
12114         }
12115         break;
12116
12117       case WhitePlay:
12118         SetWhiteToPlayEvent();
12119         break;
12120
12121       case BlackPlay:
12122         SetBlackToPlayEvent();
12123         break;
12124
12125       case EmptySquare:
12126         if (gameMode == IcsExamining) {
12127             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12128             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12129             SendToICS(buf);
12130         } else {
12131             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12132                 if(x == BOARD_LEFT-2) {
12133                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12134                     boards[0][y][1] = 0;
12135                 } else
12136                 if(x == BOARD_RGHT+1) {
12137                     if(y >= gameInfo.holdingsSize) break;
12138                     boards[0][y][BOARD_WIDTH-2] = 0;
12139                 } else break;
12140             }
12141             boards[0][y][x] = EmptySquare;
12142             DrawPosition(FALSE, boards[0]);
12143         }
12144         break;
12145
12146       case PromotePiece:
12147         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12148            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12149             selection = (ChessSquare) (PROMOTED piece);
12150         } else if(piece == EmptySquare) selection = WhiteSilver;
12151         else selection = (ChessSquare)((int)piece - 1);
12152         goto defaultlabel;
12153
12154       case DemotePiece:
12155         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12156            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12157             selection = (ChessSquare) (DEMOTED piece);
12158         } else if(piece == EmptySquare) selection = BlackSilver;
12159         else selection = (ChessSquare)((int)piece + 1);
12160         goto defaultlabel;
12161
12162       case WhiteQueen:
12163       case BlackQueen:
12164         if(gameInfo.variant == VariantShatranj ||
12165            gameInfo.variant == VariantXiangqi  ||
12166            gameInfo.variant == VariantCourier  ||
12167            gameInfo.variant == VariantMakruk     )
12168             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12169         goto defaultlabel;
12170
12171       case WhiteKing:
12172       case BlackKing:
12173         if(gameInfo.variant == VariantXiangqi)
12174             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12175         if(gameInfo.variant == VariantKnightmate)
12176             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12177       default:
12178         defaultlabel:
12179         if (gameMode == IcsExamining) {
12180             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12181             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12182                     PieceToChar(selection), AAA + x, ONE + y);
12183             SendToICS(buf);
12184         } else {
12185             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12186                 int n;
12187                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12188                     n = PieceToNumber(selection - BlackPawn);
12189                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12190                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12191                     boards[0][BOARD_HEIGHT-1-n][1]++;
12192                 } else
12193                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12194                     n = PieceToNumber(selection);
12195                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12196                     boards[0][n][BOARD_WIDTH-1] = selection;
12197                     boards[0][n][BOARD_WIDTH-2]++;
12198                 }
12199             } else
12200             boards[0][y][x] = selection;
12201             DrawPosition(TRUE, boards[0]);
12202         }
12203         break;
12204     }
12205 }
12206
12207
12208 void
12209 DropMenuEvent(selection, x, y)
12210      ChessSquare selection;
12211      int x, y;
12212 {
12213     ChessMove moveType;
12214
12215     switch (gameMode) {
12216       case IcsPlayingWhite:
12217       case MachinePlaysBlack:
12218         if (!WhiteOnMove(currentMove)) {
12219             DisplayMoveError(_("It is Black's turn"));
12220             return;
12221         }
12222         moveType = WhiteDrop;
12223         break;
12224       case IcsPlayingBlack:
12225       case MachinePlaysWhite:
12226         if (WhiteOnMove(currentMove)) {
12227             DisplayMoveError(_("It is White's turn"));
12228             return;
12229         }
12230         moveType = BlackDrop;
12231         break;
12232       case EditGame:
12233         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12234         break;
12235       default:
12236         return;
12237     }
12238
12239     if (moveType == BlackDrop && selection < BlackPawn) {
12240       selection = (ChessSquare) ((int) selection
12241                                  + (int) BlackPawn - (int) WhitePawn);
12242     }
12243     if (boards[currentMove][y][x] != EmptySquare) {
12244         DisplayMoveError(_("That square is occupied"));
12245         return;
12246     }
12247
12248     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12249 }
12250
12251 void
12252 AcceptEvent()
12253 {
12254     /* Accept a pending offer of any kind from opponent */
12255
12256     if (appData.icsActive) {
12257         SendToICS(ics_prefix);
12258         SendToICS("accept\n");
12259     } else if (cmailMsgLoaded) {
12260         if (currentMove == cmailOldMove &&
12261             commentList[cmailOldMove] != NULL &&
12262             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12263                    "Black offers a draw" : "White offers a draw")) {
12264             TruncateGame();
12265             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12266             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12267         } else {
12268             DisplayError(_("There is no pending offer on this move"), 0);
12269             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12270         }
12271     } else {
12272         /* Not used for offers from chess program */
12273     }
12274 }
12275
12276 void
12277 DeclineEvent()
12278 {
12279     /* Decline a pending offer of any kind from opponent */
12280
12281     if (appData.icsActive) {
12282         SendToICS(ics_prefix);
12283         SendToICS("decline\n");
12284     } else if (cmailMsgLoaded) {
12285         if (currentMove == cmailOldMove &&
12286             commentList[cmailOldMove] != NULL &&
12287             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12288                    "Black offers a draw" : "White offers a draw")) {
12289 #ifdef NOTDEF
12290             AppendComment(cmailOldMove, "Draw declined", TRUE);
12291             DisplayComment(cmailOldMove - 1, "Draw declined");
12292 #endif /*NOTDEF*/
12293         } else {
12294             DisplayError(_("There is no pending offer on this move"), 0);
12295         }
12296     } else {
12297         /* Not used for offers from chess program */
12298     }
12299 }
12300
12301 void
12302 RematchEvent()
12303 {
12304     /* Issue ICS rematch command */
12305     if (appData.icsActive) {
12306         SendToICS(ics_prefix);
12307         SendToICS("rematch\n");
12308     }
12309 }
12310
12311 void
12312 CallFlagEvent()
12313 {
12314     /* Call your opponent's flag (claim a win on time) */
12315     if (appData.icsActive) {
12316         SendToICS(ics_prefix);
12317         SendToICS("flag\n");
12318     } else {
12319         switch (gameMode) {
12320           default:
12321             return;
12322           case MachinePlaysWhite:
12323             if (whiteFlag) {
12324                 if (blackFlag)
12325                   GameEnds(GameIsDrawn, "Both players ran out of time",
12326                            GE_PLAYER);
12327                 else
12328                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12329             } else {
12330                 DisplayError(_("Your opponent is not out of time"), 0);
12331             }
12332             break;
12333           case MachinePlaysBlack:
12334             if (blackFlag) {
12335                 if (whiteFlag)
12336                   GameEnds(GameIsDrawn, "Both players ran out of time",
12337                            GE_PLAYER);
12338                 else
12339                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12340             } else {
12341                 DisplayError(_("Your opponent is not out of time"), 0);
12342             }
12343             break;
12344         }
12345     }
12346 }
12347
12348 void
12349 DrawEvent()
12350 {
12351     /* Offer draw or accept pending draw offer from opponent */
12352
12353     if (appData.icsActive) {
12354         /* Note: tournament rules require draw offers to be
12355            made after you make your move but before you punch
12356            your clock.  Currently ICS doesn't let you do that;
12357            instead, you immediately punch your clock after making
12358            a move, but you can offer a draw at any time. */
12359
12360         SendToICS(ics_prefix);
12361         SendToICS("draw\n");
12362         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12363     } else if (cmailMsgLoaded) {
12364         if (currentMove == cmailOldMove &&
12365             commentList[cmailOldMove] != NULL &&
12366             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12367                    "Black offers a draw" : "White offers a draw")) {
12368             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12369             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12370         } else if (currentMove == cmailOldMove + 1) {
12371             char *offer = WhiteOnMove(cmailOldMove) ?
12372               "White offers a draw" : "Black offers a draw";
12373             AppendComment(currentMove, offer, TRUE);
12374             DisplayComment(currentMove - 1, offer);
12375             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12376         } else {
12377             DisplayError(_("You must make your move before offering a draw"), 0);
12378             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12379         }
12380     } else if (first.offeredDraw) {
12381         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12382     } else {
12383         if (first.sendDrawOffers) {
12384             SendToProgram("draw\n", &first);
12385             userOfferedDraw = TRUE;
12386         }
12387     }
12388 }
12389
12390 void
12391 AdjournEvent()
12392 {
12393     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12394
12395     if (appData.icsActive) {
12396         SendToICS(ics_prefix);
12397         SendToICS("adjourn\n");
12398     } else {
12399         /* Currently GNU Chess doesn't offer or accept Adjourns */
12400     }
12401 }
12402
12403
12404 void
12405 AbortEvent()
12406 {
12407     /* Offer Abort or accept pending Abort offer from opponent */
12408
12409     if (appData.icsActive) {
12410         SendToICS(ics_prefix);
12411         SendToICS("abort\n");
12412     } else {
12413         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12414     }
12415 }
12416
12417 void
12418 ResignEvent()
12419 {
12420     /* Resign.  You can do this even if it's not your turn. */
12421
12422     if (appData.icsActive) {
12423         SendToICS(ics_prefix);
12424         SendToICS("resign\n");
12425     } else {
12426         switch (gameMode) {
12427           case MachinePlaysWhite:
12428             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12429             break;
12430           case MachinePlaysBlack:
12431             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12432             break;
12433           case EditGame:
12434             if (cmailMsgLoaded) {
12435                 TruncateGame();
12436                 if (WhiteOnMove(cmailOldMove)) {
12437                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12438                 } else {
12439                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12440                 }
12441                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12442             }
12443             break;
12444           default:
12445             break;
12446         }
12447     }
12448 }
12449
12450
12451 void
12452 StopObservingEvent()
12453 {
12454     /* Stop observing current games */
12455     SendToICS(ics_prefix);
12456     SendToICS("unobserve\n");
12457 }
12458
12459 void
12460 StopExaminingEvent()
12461 {
12462     /* Stop observing current game */
12463     SendToICS(ics_prefix);
12464     SendToICS("unexamine\n");
12465 }
12466
12467 void
12468 ForwardInner(target)
12469      int target;
12470 {
12471     int limit;
12472
12473     if (appData.debugMode)
12474         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12475                 target, currentMove, forwardMostMove);
12476
12477     if (gameMode == EditPosition)
12478       return;
12479
12480     if (gameMode == PlayFromGameFile && !pausing)
12481       PauseEvent();
12482
12483     if (gameMode == IcsExamining && pausing)
12484       limit = pauseExamForwardMostMove;
12485     else
12486       limit = forwardMostMove;
12487
12488     if (target > limit) target = limit;
12489
12490     if (target > 0 && moveList[target - 1][0]) {
12491         int fromX, fromY, toX, toY;
12492         toX = moveList[target - 1][2] - AAA;
12493         toY = moveList[target - 1][3] - ONE;
12494         if (moveList[target - 1][1] == '@') {
12495             if (appData.highlightLastMove) {
12496                 SetHighlights(-1, -1, toX, toY);
12497             }
12498         } else {
12499             fromX = moveList[target - 1][0] - AAA;
12500             fromY = moveList[target - 1][1] - ONE;
12501             if (target == currentMove + 1) {
12502                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12503             }
12504             if (appData.highlightLastMove) {
12505                 SetHighlights(fromX, fromY, toX, toY);
12506             }
12507         }
12508     }
12509     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12510         gameMode == Training || gameMode == PlayFromGameFile ||
12511         gameMode == AnalyzeFile) {
12512         while (currentMove < target) {
12513             SendMoveToProgram(currentMove++, &first);
12514         }
12515     } else {
12516         currentMove = target;
12517     }
12518
12519     if (gameMode == EditGame || gameMode == EndOfGame) {
12520         whiteTimeRemaining = timeRemaining[0][currentMove];
12521         blackTimeRemaining = timeRemaining[1][currentMove];
12522     }
12523     DisplayBothClocks();
12524     DisplayMove(currentMove - 1);
12525     DrawPosition(FALSE, boards[currentMove]);
12526     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12527     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12528         DisplayComment(currentMove - 1, commentList[currentMove]);
12529     }
12530 }
12531
12532
12533 void
12534 ForwardEvent()
12535 {
12536     if (gameMode == IcsExamining && !pausing) {
12537         SendToICS(ics_prefix);
12538         SendToICS("forward\n");
12539     } else {
12540         ForwardInner(currentMove + 1);
12541     }
12542 }
12543
12544 void
12545 ToEndEvent()
12546 {
12547     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12548         /* to optimze, we temporarily turn off analysis mode while we feed
12549          * the remaining moves to the engine. Otherwise we get analysis output
12550          * after each move.
12551          */
12552         if (first.analysisSupport) {
12553           SendToProgram("exit\nforce\n", &first);
12554           first.analyzing = FALSE;
12555         }
12556     }
12557
12558     if (gameMode == IcsExamining && !pausing) {
12559         SendToICS(ics_prefix);
12560         SendToICS("forward 999999\n");
12561     } else {
12562         ForwardInner(forwardMostMove);
12563     }
12564
12565     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12566         /* we have fed all the moves, so reactivate analysis mode */
12567         SendToProgram("analyze\n", &first);
12568         first.analyzing = TRUE;
12569         /*first.maybeThinking = TRUE;*/
12570         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12571     }
12572 }
12573
12574 void
12575 BackwardInner(target)
12576      int target;
12577 {
12578     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12579
12580     if (appData.debugMode)
12581         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12582                 target, currentMove, forwardMostMove);
12583
12584     if (gameMode == EditPosition) return;
12585     if (currentMove <= backwardMostMove) {
12586         ClearHighlights();
12587         DrawPosition(full_redraw, boards[currentMove]);
12588         return;
12589     }
12590     if (gameMode == PlayFromGameFile && !pausing)
12591       PauseEvent();
12592
12593     if (moveList[target][0]) {
12594         int fromX, fromY, toX, toY;
12595         toX = moveList[target][2] - AAA;
12596         toY = moveList[target][3] - ONE;
12597         if (moveList[target][1] == '@') {
12598             if (appData.highlightLastMove) {
12599                 SetHighlights(-1, -1, toX, toY);
12600             }
12601         } else {
12602             fromX = moveList[target][0] - AAA;
12603             fromY = moveList[target][1] - ONE;
12604             if (target == currentMove - 1) {
12605                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12606             }
12607             if (appData.highlightLastMove) {
12608                 SetHighlights(fromX, fromY, toX, toY);
12609             }
12610         }
12611     }
12612     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12613         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12614         while (currentMove > target) {
12615             SendToProgram("undo\n", &first);
12616             currentMove--;
12617         }
12618     } else {
12619         currentMove = target;
12620     }
12621
12622     if (gameMode == EditGame || gameMode == EndOfGame) {
12623         whiteTimeRemaining = timeRemaining[0][currentMove];
12624         blackTimeRemaining = timeRemaining[1][currentMove];
12625     }
12626     DisplayBothClocks();
12627     DisplayMove(currentMove - 1);
12628     DrawPosition(full_redraw, boards[currentMove]);
12629     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12630     // [HGM] PV info: routine tests if comment empty
12631     DisplayComment(currentMove - 1, commentList[currentMove]);
12632 }
12633
12634 void
12635 BackwardEvent()
12636 {
12637     if (gameMode == IcsExamining && !pausing) {
12638         SendToICS(ics_prefix);
12639         SendToICS("backward\n");
12640     } else {
12641         BackwardInner(currentMove - 1);
12642     }
12643 }
12644
12645 void
12646 ToStartEvent()
12647 {
12648     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12649         /* to optimize, we temporarily turn off analysis mode while we undo
12650          * all the moves. Otherwise we get analysis output after each undo.
12651          */
12652         if (first.analysisSupport) {
12653           SendToProgram("exit\nforce\n", &first);
12654           first.analyzing = FALSE;
12655         }
12656     }
12657
12658     if (gameMode == IcsExamining && !pausing) {
12659         SendToICS(ics_prefix);
12660         SendToICS("backward 999999\n");
12661     } else {
12662         BackwardInner(backwardMostMove);
12663     }
12664
12665     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12666         /* we have fed all the moves, so reactivate analysis mode */
12667         SendToProgram("analyze\n", &first);
12668         first.analyzing = TRUE;
12669         /*first.maybeThinking = TRUE;*/
12670         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12671     }
12672 }
12673
12674 void
12675 ToNrEvent(int to)
12676 {
12677   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12678   if (to >= forwardMostMove) to = forwardMostMove;
12679   if (to <= backwardMostMove) to = backwardMostMove;
12680   if (to < currentMove) {
12681     BackwardInner(to);
12682   } else {
12683     ForwardInner(to);
12684   }
12685 }
12686
12687 void
12688 RevertEvent()
12689 {
12690     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12691         return;
12692     }
12693     if (gameMode != IcsExamining) {
12694         DisplayError(_("You are not examining a game"), 0);
12695         return;
12696     }
12697     if (pausing) {
12698         DisplayError(_("You can't revert while pausing"), 0);
12699         return;
12700     }
12701     SendToICS(ics_prefix);
12702     SendToICS("revert\n");
12703 }
12704
12705 void
12706 RetractMoveEvent()
12707 {
12708     switch (gameMode) {
12709       case MachinePlaysWhite:
12710       case MachinePlaysBlack:
12711         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12712             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12713             return;
12714         }
12715         if (forwardMostMove < 2) return;
12716         currentMove = forwardMostMove = forwardMostMove - 2;
12717         whiteTimeRemaining = timeRemaining[0][currentMove];
12718         blackTimeRemaining = timeRemaining[1][currentMove];
12719         DisplayBothClocks();
12720         DisplayMove(currentMove - 1);
12721         ClearHighlights();/*!! could figure this out*/
12722         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12723         SendToProgram("remove\n", &first);
12724         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12725         break;
12726
12727       case BeginningOfGame:
12728       default:
12729         break;
12730
12731       case IcsPlayingWhite:
12732       case IcsPlayingBlack:
12733         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12734             SendToICS(ics_prefix);
12735             SendToICS("takeback 2\n");
12736         } else {
12737             SendToICS(ics_prefix);
12738             SendToICS("takeback 1\n");
12739         }
12740         break;
12741     }
12742 }
12743
12744 void
12745 MoveNowEvent()
12746 {
12747     ChessProgramState *cps;
12748
12749     switch (gameMode) {
12750       case MachinePlaysWhite:
12751         if (!WhiteOnMove(forwardMostMove)) {
12752             DisplayError(_("It is your turn"), 0);
12753             return;
12754         }
12755         cps = &first;
12756         break;
12757       case MachinePlaysBlack:
12758         if (WhiteOnMove(forwardMostMove)) {
12759             DisplayError(_("It is your turn"), 0);
12760             return;
12761         }
12762         cps = &first;
12763         break;
12764       case TwoMachinesPlay:
12765         if (WhiteOnMove(forwardMostMove) ==
12766             (first.twoMachinesColor[0] == 'w')) {
12767             cps = &first;
12768         } else {
12769             cps = &second;
12770         }
12771         break;
12772       case BeginningOfGame:
12773       default:
12774         return;
12775     }
12776     SendToProgram("?\n", cps);
12777 }
12778
12779 void
12780 TruncateGameEvent()
12781 {
12782     EditGameEvent();
12783     if (gameMode != EditGame) return;
12784     TruncateGame();
12785 }
12786
12787 void
12788 TruncateGame()
12789 {
12790     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12791     if (forwardMostMove > currentMove) {
12792         if (gameInfo.resultDetails != NULL) {
12793             free(gameInfo.resultDetails);
12794             gameInfo.resultDetails = NULL;
12795             gameInfo.result = GameUnfinished;
12796         }
12797         forwardMostMove = currentMove;
12798         HistorySet(parseList, backwardMostMove, forwardMostMove,
12799                    currentMove-1);
12800     }
12801 }
12802
12803 void
12804 HintEvent()
12805 {
12806     if (appData.noChessProgram) return;
12807     switch (gameMode) {
12808       case MachinePlaysWhite:
12809         if (WhiteOnMove(forwardMostMove)) {
12810             DisplayError(_("Wait until your turn"), 0);
12811             return;
12812         }
12813         break;
12814       case BeginningOfGame:
12815       case MachinePlaysBlack:
12816         if (!WhiteOnMove(forwardMostMove)) {
12817             DisplayError(_("Wait until your turn"), 0);
12818             return;
12819         }
12820         break;
12821       default:
12822         DisplayError(_("No hint available"), 0);
12823         return;
12824     }
12825     SendToProgram("hint\n", &first);
12826     hintRequested = TRUE;
12827 }
12828
12829 void
12830 BookEvent()
12831 {
12832     if (appData.noChessProgram) return;
12833     switch (gameMode) {
12834       case MachinePlaysWhite:
12835         if (WhiteOnMove(forwardMostMove)) {
12836             DisplayError(_("Wait until your turn"), 0);
12837             return;
12838         }
12839         break;
12840       case BeginningOfGame:
12841       case MachinePlaysBlack:
12842         if (!WhiteOnMove(forwardMostMove)) {
12843             DisplayError(_("Wait until your turn"), 0);
12844             return;
12845         }
12846         break;
12847       case EditPosition:
12848         EditPositionDone(TRUE);
12849         break;
12850       case TwoMachinesPlay:
12851         return;
12852       default:
12853         break;
12854     }
12855     SendToProgram("bk\n", &first);
12856     bookOutput[0] = NULLCHAR;
12857     bookRequested = TRUE;
12858 }
12859
12860 void
12861 AboutGameEvent()
12862 {
12863     char *tags = PGNTags(&gameInfo);
12864     TagsPopUp(tags, CmailMsg());
12865     free(tags);
12866 }
12867
12868 /* end button procedures */
12869
12870 void
12871 PrintPosition(fp, move)
12872      FILE *fp;
12873      int move;
12874 {
12875     int i, j;
12876
12877     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12878         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12879             char c = PieceToChar(boards[move][i][j]);
12880             fputc(c == 'x' ? '.' : c, fp);
12881             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12882         }
12883     }
12884     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12885       fprintf(fp, "white to play\n");
12886     else
12887       fprintf(fp, "black to play\n");
12888 }
12889
12890 void
12891 PrintOpponents(fp)
12892      FILE *fp;
12893 {
12894     if (gameInfo.white != NULL) {
12895         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12896     } else {
12897         fprintf(fp, "\n");
12898     }
12899 }
12900
12901 /* Find last component of program's own name, using some heuristics */
12902 void
12903 TidyProgramName(prog, host, buf)
12904      char *prog, *host, buf[MSG_SIZ];
12905 {
12906     char *p, *q;
12907     int local = (strcmp(host, "localhost") == 0);
12908     while (!local && (p = strchr(prog, ';')) != NULL) {
12909         p++;
12910         while (*p == ' ') p++;
12911         prog = p;
12912     }
12913     if (*prog == '"' || *prog == '\'') {
12914         q = strchr(prog + 1, *prog);
12915     } else {
12916         q = strchr(prog, ' ');
12917     }
12918     if (q == NULL) q = prog + strlen(prog);
12919     p = q;
12920     while (p >= prog && *p != '/' && *p != '\\') p--;
12921     p++;
12922     if(p == prog && *p == '"') p++;
12923     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12924     memcpy(buf, p, q - p);
12925     buf[q - p] = NULLCHAR;
12926     if (!local) {
12927         strcat(buf, "@");
12928         strcat(buf, host);
12929     }
12930 }
12931
12932 char *
12933 TimeControlTagValue()
12934 {
12935     char buf[MSG_SIZ];
12936     if (!appData.clockMode) {
12937         strcpy(buf, "-");
12938     } else if (movesPerSession > 0) {
12939         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12940     } else if (timeIncrement == 0) {
12941         sprintf(buf, "%ld", timeControl/1000);
12942     } else {
12943         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12944     }
12945     return StrSave(buf);
12946 }
12947
12948 void
12949 SetGameInfo()
12950 {
12951     /* This routine is used only for certain modes */
12952     VariantClass v = gameInfo.variant;
12953     ChessMove r = GameUnfinished;
12954     char *p = NULL;
12955
12956     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12957         r = gameInfo.result; 
12958         p = gameInfo.resultDetails; 
12959         gameInfo.resultDetails = NULL;
12960     }
12961     ClearGameInfo(&gameInfo);
12962     gameInfo.variant = v;
12963
12964     switch (gameMode) {
12965       case MachinePlaysWhite:
12966         gameInfo.event = StrSave( appData.pgnEventHeader );
12967         gameInfo.site = StrSave(HostName());
12968         gameInfo.date = PGNDate();
12969         gameInfo.round = StrSave("-");
12970         gameInfo.white = StrSave(first.tidy);
12971         gameInfo.black = StrSave(UserName());
12972         gameInfo.timeControl = TimeControlTagValue();
12973         break;
12974
12975       case MachinePlaysBlack:
12976         gameInfo.event = StrSave( appData.pgnEventHeader );
12977         gameInfo.site = StrSave(HostName());
12978         gameInfo.date = PGNDate();
12979         gameInfo.round = StrSave("-");
12980         gameInfo.white = StrSave(UserName());
12981         gameInfo.black = StrSave(first.tidy);
12982         gameInfo.timeControl = TimeControlTagValue();
12983         break;
12984
12985       case TwoMachinesPlay:
12986         gameInfo.event = StrSave( appData.pgnEventHeader );
12987         gameInfo.site = StrSave(HostName());
12988         gameInfo.date = PGNDate();
12989         if (matchGame > 0) {
12990             char buf[MSG_SIZ];
12991             sprintf(buf, "%d", matchGame);
12992             gameInfo.round = StrSave(buf);
12993         } else {
12994             gameInfo.round = StrSave("-");
12995         }
12996         if (first.twoMachinesColor[0] == 'w') {
12997             gameInfo.white = StrSave(first.tidy);
12998             gameInfo.black = StrSave(second.tidy);
12999         } else {
13000             gameInfo.white = StrSave(second.tidy);
13001             gameInfo.black = StrSave(first.tidy);
13002         }
13003         gameInfo.timeControl = TimeControlTagValue();
13004         break;
13005
13006       case EditGame:
13007         gameInfo.event = StrSave("Edited game");
13008         gameInfo.site = StrSave(HostName());
13009         gameInfo.date = PGNDate();
13010         gameInfo.round = StrSave("-");
13011         gameInfo.white = StrSave("-");
13012         gameInfo.black = StrSave("-");
13013         gameInfo.result = r;
13014         gameInfo.resultDetails = p;
13015         break;
13016
13017       case EditPosition:
13018         gameInfo.event = StrSave("Edited position");
13019         gameInfo.site = StrSave(HostName());
13020         gameInfo.date = PGNDate();
13021         gameInfo.round = StrSave("-");
13022         gameInfo.white = StrSave("-");
13023         gameInfo.black = StrSave("-");
13024         break;
13025
13026       case IcsPlayingWhite:
13027       case IcsPlayingBlack:
13028       case IcsObserving:
13029       case IcsExamining:
13030         break;
13031
13032       case PlayFromGameFile:
13033         gameInfo.event = StrSave("Game from non-PGN file");
13034         gameInfo.site = StrSave(HostName());
13035         gameInfo.date = PGNDate();
13036         gameInfo.round = StrSave("-");
13037         gameInfo.white = StrSave("?");
13038         gameInfo.black = StrSave("?");
13039         break;
13040
13041       default:
13042         break;
13043     }
13044 }
13045
13046 void
13047 ReplaceComment(index, text)
13048      int index;
13049      char *text;
13050 {
13051     int len;
13052
13053     while (*text == '\n') text++;
13054     len = strlen(text);
13055     while (len > 0 && text[len - 1] == '\n') len--;
13056
13057     if (commentList[index] != NULL)
13058       free(commentList[index]);
13059
13060     if (len == 0) {
13061         commentList[index] = NULL;
13062         return;
13063     }
13064   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13065       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13066       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13067     commentList[index] = (char *) malloc(len + 2);
13068     strncpy(commentList[index], text, len);
13069     commentList[index][len] = '\n';
13070     commentList[index][len + 1] = NULLCHAR;
13071   } else { 
13072     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13073     char *p;
13074     commentList[index] = (char *) malloc(len + 6);
13075     strcpy(commentList[index], "{\n");
13076     strncpy(commentList[index]+2, text, len);
13077     commentList[index][len+2] = NULLCHAR;
13078     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13079     strcat(commentList[index], "\n}\n");
13080   }
13081 }
13082
13083 void
13084 CrushCRs(text)
13085      char *text;
13086 {
13087   char *p = text;
13088   char *q = text;
13089   char ch;
13090
13091   do {
13092     ch = *p++;
13093     if (ch == '\r') continue;
13094     *q++ = ch;
13095   } while (ch != '\0');
13096 }
13097
13098 void
13099 AppendComment(index, text, addBraces)
13100      int index;
13101      char *text;
13102      Boolean addBraces; // [HGM] braces: tells if we should add {}
13103 {
13104     int oldlen, len;
13105     char *old;
13106
13107 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13108     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13109
13110     CrushCRs(text);
13111     while (*text == '\n') text++;
13112     len = strlen(text);
13113     while (len > 0 && text[len - 1] == '\n') len--;
13114
13115     if (len == 0) return;
13116
13117     if (commentList[index] != NULL) {
13118         old = commentList[index];
13119         oldlen = strlen(old);
13120         while(commentList[index][oldlen-1] ==  '\n')
13121           commentList[index][--oldlen] = NULLCHAR;
13122         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13123         strcpy(commentList[index], old);
13124         free(old);
13125         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13126         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13127           if(addBraces) addBraces = FALSE; else { text++; len--; }
13128           while (*text == '\n') { text++; len--; }
13129           commentList[index][--oldlen] = NULLCHAR;
13130       }
13131         if(addBraces) strcat(commentList[index], "\n{\n");
13132         else          strcat(commentList[index], "\n");
13133         strcat(commentList[index], text);
13134         if(addBraces) strcat(commentList[index], "\n}\n");
13135         else          strcat(commentList[index], "\n");
13136     } else {
13137         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13138         if(addBraces)
13139              strcpy(commentList[index], "{\n");
13140         else commentList[index][0] = NULLCHAR;
13141         strcat(commentList[index], text);
13142         strcat(commentList[index], "\n");
13143         if(addBraces) strcat(commentList[index], "}\n");
13144     }
13145 }
13146
13147 static char * FindStr( char * text, char * sub_text )
13148 {
13149     char * result = strstr( text, sub_text );
13150
13151     if( result != NULL ) {
13152         result += strlen( sub_text );
13153     }
13154
13155     return result;
13156 }
13157
13158 /* [AS] Try to extract PV info from PGN comment */
13159 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13160 char *GetInfoFromComment( int index, char * text )
13161 {
13162     char * sep = text;
13163
13164     if( text != NULL && index > 0 ) {
13165         int score = 0;
13166         int depth = 0;
13167         int time = -1, sec = 0, deci;
13168         char * s_eval = FindStr( text, "[%eval " );
13169         char * s_emt = FindStr( text, "[%emt " );
13170
13171         if( s_eval != NULL || s_emt != NULL ) {
13172             /* New style */
13173             char delim;
13174
13175             if( s_eval != NULL ) {
13176                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13177                     return text;
13178                 }
13179
13180                 if( delim != ']' ) {
13181                     return text;
13182                 }
13183             }
13184
13185             if( s_emt != NULL ) {
13186             }
13187                 return text;
13188         }
13189         else {
13190             /* We expect something like: [+|-]nnn.nn/dd */
13191             int score_lo = 0;
13192
13193             if(*text != '{') return text; // [HGM] braces: must be normal comment
13194
13195             sep = strchr( text, '/' );
13196             if( sep == NULL || sep < (text+4) ) {
13197                 return text;
13198             }
13199
13200             time = -1; sec = -1; deci = -1;
13201             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13202                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13203                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13204                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13205                 return text;
13206             }
13207
13208             if( score_lo < 0 || score_lo >= 100 ) {
13209                 return text;
13210             }
13211
13212             if(sec >= 0) time = 600*time + 10*sec; else
13213             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13214
13215             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13216
13217             /* [HGM] PV time: now locate end of PV info */
13218             while( *++sep >= '0' && *sep <= '9'); // strip depth
13219             if(time >= 0)
13220             while( *++sep >= '0' && *sep <= '9'); // strip time
13221             if(sec >= 0)
13222             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13223             if(deci >= 0)
13224             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13225             while(*sep == ' ') sep++;
13226         }
13227
13228         if( depth <= 0 ) {
13229             return text;
13230         }
13231
13232         if( time < 0 ) {
13233             time = -1;
13234         }
13235
13236         pvInfoList[index-1].depth = depth;
13237         pvInfoList[index-1].score = score;
13238         pvInfoList[index-1].time  = 10*time; // centi-sec
13239         if(*sep == '}') *sep = 0; else *--sep = '{';
13240     }
13241     return sep;
13242 }
13243
13244 void
13245 SendToProgram(message, cps)
13246      char *message;
13247      ChessProgramState *cps;
13248 {
13249     int count, outCount, error;
13250     char buf[MSG_SIZ];
13251
13252     if (cps->pr == NULL) return;
13253     Attention(cps);
13254
13255     if (appData.debugMode) {
13256         TimeMark now;
13257         GetTimeMark(&now);
13258         fprintf(debugFP, "%ld >%-6s: %s",
13259                 SubtractTimeMarks(&now, &programStartTime),
13260                 cps->which, message);
13261     }
13262
13263     count = strlen(message);
13264     outCount = OutputToProcess(cps->pr, message, count, &error);
13265     if (outCount < count && !exiting
13266                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13267         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13268         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13269             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13270                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13271                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13272             } else {
13273                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13274             }
13275             gameInfo.resultDetails = StrSave(buf);
13276         }
13277         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13278     }
13279 }
13280
13281 void
13282 ReceiveFromProgram(isr, closure, message, count, error)
13283      InputSourceRef isr;
13284      VOIDSTAR closure;
13285      char *message;
13286      int count;
13287      int error;
13288 {
13289     char *end_str;
13290     char buf[MSG_SIZ];
13291     ChessProgramState *cps = (ChessProgramState *)closure;
13292
13293     if (isr != cps->isr) return; /* Killed intentionally */
13294     if (count <= 0) {
13295         if (count == 0) {
13296             sprintf(buf,
13297                     _("Error: %s chess program (%s) exited unexpectedly"),
13298                     cps->which, cps->program);
13299         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13300                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13301                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13302                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13303                 } else {
13304                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13305                 }
13306                 gameInfo.resultDetails = StrSave(buf);
13307             }
13308             RemoveInputSource(cps->isr);
13309             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13310         } else {
13311             sprintf(buf,
13312                     _("Error reading from %s chess program (%s)"),
13313                     cps->which, cps->program);
13314             RemoveInputSource(cps->isr);
13315
13316             /* [AS] Program is misbehaving badly... kill it */
13317             if( count == -2 ) {
13318                 DestroyChildProcess( cps->pr, 9 );
13319                 cps->pr = NoProc;
13320             }
13321
13322             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13323         }
13324         return;
13325     }
13326
13327     if ((end_str = strchr(message, '\r')) != NULL)
13328       *end_str = NULLCHAR;
13329     if ((end_str = strchr(message, '\n')) != NULL)
13330       *end_str = NULLCHAR;
13331
13332     if (appData.debugMode) {
13333         TimeMark now; int print = 1;
13334         char *quote = ""; char c; int i;
13335
13336         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13337                 char start = message[0];
13338                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13339                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13340                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13341                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13342                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13343                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13344                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13345                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13346                         { quote = "# "; print = (appData.engineComments == 2); }
13347                 message[0] = start; // restore original message
13348         }
13349         if(print) {
13350                 GetTimeMark(&now);
13351                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13352                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13353                         quote,
13354                         message);
13355         }
13356     }
13357
13358     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13359     if (appData.icsEngineAnalyze) {
13360         if (strstr(message, "whisper") != NULL ||
13361              strstr(message, "kibitz") != NULL ||
13362             strstr(message, "tellics") != NULL) return;
13363     }
13364
13365     HandleMachineMove(message, cps);
13366 }
13367
13368
13369 void
13370 SendTimeControl(cps, mps, tc, inc, sd, st)
13371      ChessProgramState *cps;
13372      int mps, inc, sd, st;
13373      long tc;
13374 {
13375     char buf[MSG_SIZ];
13376     int seconds;
13377
13378     if( timeControl_2 > 0 ) {
13379         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13380             tc = timeControl_2;
13381         }
13382     }
13383     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13384     inc /= cps->timeOdds;
13385     st  /= cps->timeOdds;
13386
13387     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13388
13389     if (st > 0) {
13390       /* Set exact time per move, normally using st command */
13391       if (cps->stKludge) {
13392         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13393         seconds = st % 60;
13394         if (seconds == 0) {
13395           sprintf(buf, "level 1 %d\n", st/60);
13396         } else {
13397           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13398         }
13399       } else {
13400         sprintf(buf, "st %d\n", st);
13401       }
13402     } else {
13403       /* Set conventional or incremental time control, using level command */
13404       if (seconds == 0) {
13405         /* Note old gnuchess bug -- minutes:seconds used to not work.
13406            Fixed in later versions, but still avoid :seconds
13407            when seconds is 0. */
13408         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13409       } else {
13410         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13411                 seconds, inc/1000);
13412       }
13413     }
13414     SendToProgram(buf, cps);
13415
13416     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13417     /* Orthogonally, limit search to given depth */
13418     if (sd > 0) {
13419       if (cps->sdKludge) {
13420         sprintf(buf, "depth\n%d\n", sd);
13421       } else {
13422         sprintf(buf, "sd %d\n", sd);
13423       }
13424       SendToProgram(buf, cps);
13425     }
13426
13427     if(cps->nps > 0) { /* [HGM] nps */
13428         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13429         else {
13430                 sprintf(buf, "nps %d\n", cps->nps);
13431               SendToProgram(buf, cps);
13432         }
13433     }
13434 }
13435
13436 ChessProgramState *WhitePlayer()
13437 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13438 {
13439     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13440        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13441         return &second;
13442     return &first;
13443 }
13444
13445 void
13446 SendTimeRemaining(cps, machineWhite)
13447      ChessProgramState *cps;
13448      int /*boolean*/ machineWhite;
13449 {
13450     char message[MSG_SIZ];
13451     long time, otime;
13452
13453     /* Note: this routine must be called when the clocks are stopped
13454        or when they have *just* been set or switched; otherwise
13455        it will be off by the time since the current tick started.
13456     */
13457     if (machineWhite) {
13458         time = whiteTimeRemaining / 10;
13459         otime = blackTimeRemaining / 10;
13460     } else {
13461         time = blackTimeRemaining / 10;
13462         otime = whiteTimeRemaining / 10;
13463     }
13464     /* [HGM] translate opponent's time by time-odds factor */
13465     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13466     if (appData.debugMode) {
13467         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13468     }
13469
13470     if (time <= 0) time = 1;
13471     if (otime <= 0) otime = 1;
13472
13473     sprintf(message, "time %ld\n", time);
13474     SendToProgram(message, cps);
13475
13476     sprintf(message, "otim %ld\n", otime);
13477     SendToProgram(message, cps);
13478 }
13479
13480 int
13481 BoolFeature(p, name, loc, cps)
13482      char **p;
13483      char *name;
13484      int *loc;
13485      ChessProgramState *cps;
13486 {
13487   char buf[MSG_SIZ];
13488   int len = strlen(name);
13489   int val;
13490   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13491     (*p) += len + 1;
13492     sscanf(*p, "%d", &val);
13493     *loc = (val != 0);
13494     while (**p && **p != ' ') (*p)++;
13495     sprintf(buf, "accepted %s\n", name);
13496     SendToProgram(buf, cps);
13497     return TRUE;
13498   }
13499   return FALSE;
13500 }
13501
13502 int
13503 IntFeature(p, name, loc, cps)
13504      char **p;
13505      char *name;
13506      int *loc;
13507      ChessProgramState *cps;
13508 {
13509   char buf[MSG_SIZ];
13510   int len = strlen(name);
13511   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13512     (*p) += len + 1;
13513     sscanf(*p, "%d", loc);
13514     while (**p && **p != ' ') (*p)++;
13515     sprintf(buf, "accepted %s\n", name);
13516     SendToProgram(buf, cps);
13517     return TRUE;
13518   }
13519   return FALSE;
13520 }
13521
13522 int
13523 StringFeature(p, name, loc, cps)
13524      char **p;
13525      char *name;
13526      char loc[];
13527      ChessProgramState *cps;
13528 {
13529   char buf[MSG_SIZ];
13530   int len = strlen(name);
13531   if (strncmp((*p), name, len) == 0
13532       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13533     (*p) += len + 2;
13534     sscanf(*p, "%[^\"]", loc);
13535     while (**p && **p != '\"') (*p)++;
13536     if (**p == '\"') (*p)++;
13537     sprintf(buf, "accepted %s\n", name);
13538     SendToProgram(buf, cps);
13539     return TRUE;
13540   }
13541   return FALSE;
13542 }
13543
13544 int
13545 ParseOption(Option *opt, ChessProgramState *cps)
13546 // [HGM] options: process the string that defines an engine option, and determine
13547 // name, type, default value, and allowed value range
13548 {
13549         char *p, *q, buf[MSG_SIZ];
13550         int n, min = (-1)<<31, max = 1<<31, def;
13551
13552         if(p = strstr(opt->name, " -spin ")) {
13553             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13554             if(max < min) max = min; // enforce consistency
13555             if(def < min) def = min;
13556             if(def > max) def = max;
13557             opt->value = def;
13558             opt->min = min;
13559             opt->max = max;
13560             opt->type = Spin;
13561         } else if((p = strstr(opt->name, " -slider "))) {
13562             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13563             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13564             if(max < min) max = min; // enforce consistency
13565             if(def < min) def = min;
13566             if(def > max) def = max;
13567             opt->value = def;
13568             opt->min = min;
13569             opt->max = max;
13570             opt->type = Spin; // Slider;
13571         } else if((p = strstr(opt->name, " -string "))) {
13572             opt->textValue = p+9;
13573             opt->type = TextBox;
13574         } else if((p = strstr(opt->name, " -file "))) {
13575             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13576             opt->textValue = p+7;
13577             opt->type = TextBox; // FileName;
13578         } else if((p = strstr(opt->name, " -path "))) {
13579             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13580             opt->textValue = p+7;
13581             opt->type = TextBox; // PathName;
13582         } else if(p = strstr(opt->name, " -check ")) {
13583             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13584             opt->value = (def != 0);
13585             opt->type = CheckBox;
13586         } else if(p = strstr(opt->name, " -combo ")) {
13587             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13588             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13589             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13590             opt->value = n = 0;
13591             while(q = StrStr(q, " /// ")) {
13592                 n++; *q = 0;    // count choices, and null-terminate each of them
13593                 q += 5;
13594                 if(*q == '*') { // remember default, which is marked with * prefix
13595                     q++;
13596                     opt->value = n;
13597                 }
13598                 cps->comboList[cps->comboCnt++] = q;
13599             }
13600             cps->comboList[cps->comboCnt++] = NULL;
13601             opt->max = n + 1;
13602             opt->type = ComboBox;
13603         } else if(p = strstr(opt->name, " -button")) {
13604             opt->type = Button;
13605         } else if(p = strstr(opt->name, " -save")) {
13606             opt->type = SaveButton;
13607         } else return FALSE;
13608         *p = 0; // terminate option name
13609         // now look if the command-line options define a setting for this engine option.
13610         if(cps->optionSettings && cps->optionSettings[0])
13611             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13612         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13613                 sprintf(buf, "option %s", p);
13614                 if(p = strstr(buf, ",")) *p = 0;
13615                 strcat(buf, "\n");
13616                 SendToProgram(buf, cps);
13617         }
13618         return TRUE;
13619 }
13620
13621 void
13622 FeatureDone(cps, val)
13623      ChessProgramState* cps;
13624      int val;
13625 {
13626   DelayedEventCallback cb = GetDelayedEvent();
13627   if ((cb == InitBackEnd3 && cps == &first) ||
13628       (cb == TwoMachinesEventIfReady && cps == &second)) {
13629     CancelDelayedEvent();
13630     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13631   }
13632   cps->initDone = val;
13633 }
13634
13635 /* Parse feature command from engine */
13636 void
13637 ParseFeatures(args, cps)
13638      char* args;
13639      ChessProgramState *cps;
13640 {
13641   char *p = args;
13642   char *q;
13643   int val;
13644   char buf[MSG_SIZ];
13645
13646   for (;;) {
13647     while (*p == ' ') p++;
13648     if (*p == NULLCHAR) return;
13649
13650     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13651     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13652     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13653     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13654     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13655     if (BoolFeature(&p, "reuse", &val, cps)) {
13656       /* Engine can disable reuse, but can't enable it if user said no */
13657       if (!val) cps->reuse = FALSE;
13658       continue;
13659     }
13660     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13661     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13662       if (gameMode == TwoMachinesPlay) {
13663         DisplayTwoMachinesTitle();
13664       } else {
13665         DisplayTitle("");
13666       }
13667       continue;
13668     }
13669     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13670     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13671     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13672     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13673     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13674     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13675     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13676     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13677     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13678     if (IntFeature(&p, "done", &val, cps)) {
13679       FeatureDone(cps, val);
13680       continue;
13681     }
13682     /* Added by Tord: */
13683     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13684     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13685     /* End of additions by Tord */
13686
13687     /* [HGM] added features: */
13688     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13689     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13690     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13691     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13692     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13693     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13694     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13695         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13696             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13697             SendToProgram(buf, cps);
13698             continue;
13699         }
13700         if(cps->nrOptions >= MAX_OPTIONS) {
13701             cps->nrOptions--;
13702             sprintf(buf, "%s engine has too many options\n", cps->which);
13703             DisplayError(buf, 0);
13704         }
13705         continue;
13706     }
13707     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13708     /* End of additions by HGM */
13709
13710     /* unknown feature: complain and skip */
13711     q = p;
13712     while (*q && *q != '=') q++;
13713     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13714     SendToProgram(buf, cps);
13715     p = q;
13716     if (*p == '=') {
13717       p++;
13718       if (*p == '\"') {
13719         p++;
13720         while (*p && *p != '\"') p++;
13721         if (*p == '\"') p++;
13722       } else {
13723         while (*p && *p != ' ') p++;
13724       }
13725     }
13726   }
13727
13728 }
13729
13730 void
13731 PeriodicUpdatesEvent(newState)
13732      int newState;
13733 {
13734     if (newState == appData.periodicUpdates)
13735       return;
13736
13737     appData.periodicUpdates=newState;
13738
13739     /* Display type changes, so update it now */
13740 //    DisplayAnalysis();
13741
13742     /* Get the ball rolling again... */
13743     if (newState) {
13744         AnalysisPeriodicEvent(1);
13745         StartAnalysisClock();
13746     }
13747 }
13748
13749 void
13750 PonderNextMoveEvent(newState)
13751      int newState;
13752 {
13753     if (newState == appData.ponderNextMove) return;
13754     if (gameMode == EditPosition) EditPositionDone(TRUE);
13755     if (newState) {
13756         SendToProgram("hard\n", &first);
13757         if (gameMode == TwoMachinesPlay) {
13758             SendToProgram("hard\n", &second);
13759         }
13760     } else {
13761         SendToProgram("easy\n", &first);
13762         thinkOutput[0] = NULLCHAR;
13763         if (gameMode == TwoMachinesPlay) {
13764             SendToProgram("easy\n", &second);
13765         }
13766     }
13767     appData.ponderNextMove = newState;
13768 }
13769
13770 void
13771 NewSettingEvent(option, command, value)
13772      char *command;
13773      int option, value;
13774 {
13775     char buf[MSG_SIZ];
13776
13777     if (gameMode == EditPosition) EditPositionDone(TRUE);
13778     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13779     SendToProgram(buf, &first);
13780     if (gameMode == TwoMachinesPlay) {
13781         SendToProgram(buf, &second);
13782     }
13783 }
13784
13785 void
13786 ShowThinkingEvent()
13787 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13788 {
13789     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13790     int newState = appData.showThinking
13791         // [HGM] thinking: other features now need thinking output as well
13792         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13793
13794     if (oldState == newState) return;
13795     oldState = newState;
13796     if (gameMode == EditPosition) EditPositionDone(TRUE);
13797     if (oldState) {
13798         SendToProgram("post\n", &first);
13799         if (gameMode == TwoMachinesPlay) {
13800             SendToProgram("post\n", &second);
13801         }
13802     } else {
13803         SendToProgram("nopost\n", &first);
13804         thinkOutput[0] = NULLCHAR;
13805         if (gameMode == TwoMachinesPlay) {
13806             SendToProgram("nopost\n", &second);
13807         }
13808     }
13809 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13810 }
13811
13812 void
13813 AskQuestionEvent(title, question, replyPrefix, which)
13814      char *title; char *question; char *replyPrefix; char *which;
13815 {
13816   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13817   if (pr == NoProc) return;
13818   AskQuestion(title, question, replyPrefix, pr);
13819 }
13820
13821 void
13822 DisplayMove(moveNumber)
13823      int moveNumber;
13824 {
13825     char message[MSG_SIZ];
13826     char res[MSG_SIZ];
13827     char cpThinkOutput[MSG_SIZ];
13828
13829     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13830
13831     if (moveNumber == forwardMostMove - 1 ||
13832         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13833
13834         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13835
13836         if (strchr(cpThinkOutput, '\n')) {
13837             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13838         }
13839     } else {
13840         *cpThinkOutput = NULLCHAR;
13841     }
13842
13843     /* [AS] Hide thinking from human user */
13844     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13845         *cpThinkOutput = NULLCHAR;
13846         if( thinkOutput[0] != NULLCHAR ) {
13847             int i;
13848
13849             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13850                 cpThinkOutput[i] = '.';
13851             }
13852             cpThinkOutput[i] = NULLCHAR;
13853             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13854         }
13855     }
13856
13857     if (moveNumber == forwardMostMove - 1 &&
13858         gameInfo.resultDetails != NULL) {
13859         if (gameInfo.resultDetails[0] == NULLCHAR) {
13860             sprintf(res, " %s", PGNResult(gameInfo.result));
13861         } else {
13862             sprintf(res, " {%s} %s",
13863                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13864         }
13865     } else {
13866         res[0] = NULLCHAR;
13867     }
13868
13869     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13870         DisplayMessage(res, cpThinkOutput);
13871     } else {
13872         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13873                 WhiteOnMove(moveNumber) ? " " : ".. ",
13874                 parseList[moveNumber], res);
13875         DisplayMessage(message, cpThinkOutput);
13876     }
13877 }
13878
13879 void
13880 DisplayComment(moveNumber, text)
13881      int moveNumber;
13882      char *text;
13883 {
13884     char title[MSG_SIZ];
13885     char buf[8000]; // comment can be long!
13886     int score, depth;
13887     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13888       strcpy(title, "Comment");
13889     } else {
13890       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13891               WhiteOnMove(moveNumber) ? " " : ".. ",
13892               parseList[moveNumber]);
13893     }
13894     // [HGM] PV info: display PV info together with (or as) comment
13895     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13896       if(text == NULL) text = "";                                           
13897       score = pvInfoList[moveNumber].score;
13898       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13899               depth, (pvInfoList[moveNumber].time+50)/100, text);
13900       text = buf;
13901     }
13902     if (text != NULL && (appData.autoDisplayComment || commentUp))
13903       CommentPopUp(title, text);
13904 }
13905
13906 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13907  * might be busy thinking or pondering.  It can be omitted if your
13908  * gnuchess is configured to stop thinking immediately on any user
13909  * input.  However, that gnuchess feature depends on the FIONREAD
13910  * ioctl, which does not work properly on some flavors of Unix.
13911  */
13912 void
13913 Attention(cps)
13914      ChessProgramState *cps;
13915 {
13916 #if ATTENTION
13917     if (!cps->useSigint) return;
13918     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13919     switch (gameMode) {
13920       case MachinePlaysWhite:
13921       case MachinePlaysBlack:
13922       case TwoMachinesPlay:
13923       case IcsPlayingWhite:
13924       case IcsPlayingBlack:
13925       case AnalyzeMode:
13926       case AnalyzeFile:
13927         /* Skip if we know it isn't thinking */
13928         if (!cps->maybeThinking) return;
13929         if (appData.debugMode)
13930           fprintf(debugFP, "Interrupting %s\n", cps->which);
13931         InterruptChildProcess(cps->pr);
13932         cps->maybeThinking = FALSE;
13933         break;
13934       default:
13935         break;
13936     }
13937 #endif /*ATTENTION*/
13938 }
13939
13940 int
13941 CheckFlags()
13942 {
13943     if (whiteTimeRemaining <= 0) {
13944         if (!whiteFlag) {
13945             whiteFlag = TRUE;
13946             if (appData.icsActive) {
13947                 if (appData.autoCallFlag &&
13948                     gameMode == IcsPlayingBlack && !blackFlag) {
13949                   SendToICS(ics_prefix);
13950                   SendToICS("flag\n");
13951                 }
13952             } else {
13953                 if (blackFlag) {
13954                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13955                 } else {
13956                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13957                     if (appData.autoCallFlag) {
13958                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13959                         return TRUE;
13960                     }
13961                 }
13962             }
13963         }
13964     }
13965     if (blackTimeRemaining <= 0) {
13966         if (!blackFlag) {
13967             blackFlag = TRUE;
13968             if (appData.icsActive) {
13969                 if (appData.autoCallFlag &&
13970                     gameMode == IcsPlayingWhite && !whiteFlag) {
13971                   SendToICS(ics_prefix);
13972                   SendToICS("flag\n");
13973                 }
13974             } else {
13975                 if (whiteFlag) {
13976                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13977                 } else {
13978                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13979                     if (appData.autoCallFlag) {
13980                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13981                         return TRUE;
13982                     }
13983                 }
13984             }
13985         }
13986     }
13987     return FALSE;
13988 }
13989
13990 void
13991 CheckTimeControl()
13992 {
13993     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13994         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13995
13996     /*
13997      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13998      */
13999     if ( !WhiteOnMove(forwardMostMove) )
14000         /* White made time control */
14001         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14002         /* [HGM] time odds: correct new time quota for time odds! */
14003                                             / WhitePlayer()->timeOdds;
14004       else
14005         /* Black made time control */
14006         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14007                                             / WhitePlayer()->other->timeOdds;
14008 }
14009
14010 void
14011 DisplayBothClocks()
14012 {
14013     int wom = gameMode == EditPosition ?
14014       !blackPlaysFirst : WhiteOnMove(currentMove);
14015     DisplayWhiteClock(whiteTimeRemaining, wom);
14016     DisplayBlackClock(blackTimeRemaining, !wom);
14017 }
14018
14019
14020 /* Timekeeping seems to be a portability nightmare.  I think everyone
14021    has ftime(), but I'm really not sure, so I'm including some ifdefs
14022    to use other calls if you don't.  Clocks will be less accurate if
14023    you have neither ftime nor gettimeofday.
14024 */
14025
14026 /* VS 2008 requires the #include outside of the function */
14027 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14028 #include <sys/timeb.h>
14029 #endif
14030
14031 /* Get the current time as a TimeMark */
14032 void
14033 GetTimeMark(tm)
14034      TimeMark *tm;
14035 {
14036 #if HAVE_GETTIMEOFDAY
14037
14038     struct timeval timeVal;
14039     struct timezone timeZone;
14040
14041     gettimeofday(&timeVal, &timeZone);
14042     tm->sec = (long) timeVal.tv_sec;
14043     tm->ms = (int) (timeVal.tv_usec / 1000L);
14044
14045 #else /*!HAVE_GETTIMEOFDAY*/
14046 #if HAVE_FTIME
14047
14048 // include <sys/timeb.h> / moved to just above start of function
14049     struct timeb timeB;
14050
14051     ftime(&timeB);
14052     tm->sec = (long) timeB.time;
14053     tm->ms = (int) timeB.millitm;
14054
14055 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14056     tm->sec = (long) time(NULL);
14057     tm->ms = 0;
14058 #endif
14059 #endif
14060 }
14061
14062 /* Return the difference in milliseconds between two
14063    time marks.  We assume the difference will fit in a long!
14064 */
14065 long
14066 SubtractTimeMarks(tm2, tm1)
14067      TimeMark *tm2, *tm1;
14068 {
14069     return 1000L*(tm2->sec - tm1->sec) +
14070            (long) (tm2->ms - tm1->ms);
14071 }
14072
14073
14074 /*
14075  * Code to manage the game clocks.
14076  *
14077  * In tournament play, black starts the clock and then white makes a move.
14078  * We give the human user a slight advantage if he is playing white---the
14079  * clocks don't run until he makes his first move, so it takes zero time.
14080  * Also, we don't account for network lag, so we could get out of sync
14081  * with GNU Chess's clock -- but then, referees are always right.
14082  */
14083
14084 static TimeMark tickStartTM;
14085 static long intendedTickLength;
14086
14087 long
14088 NextTickLength(timeRemaining)
14089      long timeRemaining;
14090 {
14091     long nominalTickLength, nextTickLength;
14092
14093     if (timeRemaining > 0L && timeRemaining <= 10000L)
14094       nominalTickLength = 100L;
14095     else
14096       nominalTickLength = 1000L;
14097     nextTickLength = timeRemaining % nominalTickLength;
14098     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14099
14100     return nextTickLength;
14101 }
14102
14103 /* Adjust clock one minute up or down */
14104 void
14105 AdjustClock(Boolean which, int dir)
14106 {
14107     if(which) blackTimeRemaining += 60000*dir;
14108     else      whiteTimeRemaining += 60000*dir;
14109     DisplayBothClocks();
14110 }
14111
14112 /* Stop clocks and reset to a fresh time control */
14113 void
14114 ResetClocks()
14115 {
14116     (void) StopClockTimer();
14117     if (appData.icsActive) {
14118         whiteTimeRemaining = blackTimeRemaining = 0;
14119     } else if (searchTime) {
14120         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14121         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14122     } else { /* [HGM] correct new time quote for time odds */
14123         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14124         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14125     }
14126     if (whiteFlag || blackFlag) {
14127         DisplayTitle("");
14128         whiteFlag = blackFlag = FALSE;
14129     }
14130     DisplayBothClocks();
14131 }
14132
14133 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14134
14135 /* Decrement running clock by amount of time that has passed */
14136 void
14137 DecrementClocks()
14138 {
14139     long timeRemaining;
14140     long lastTickLength, fudge;
14141     TimeMark now;
14142
14143     if (!appData.clockMode) return;
14144     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14145
14146     GetTimeMark(&now);
14147
14148     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14149
14150     /* Fudge if we woke up a little too soon */
14151     fudge = intendedTickLength - lastTickLength;
14152     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14153
14154     if (WhiteOnMove(forwardMostMove)) {
14155         if(whiteNPS >= 0) lastTickLength = 0;
14156         timeRemaining = whiteTimeRemaining -= lastTickLength;
14157         DisplayWhiteClock(whiteTimeRemaining - fudge,
14158                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14159     } else {
14160         if(blackNPS >= 0) lastTickLength = 0;
14161         timeRemaining = blackTimeRemaining -= lastTickLength;
14162         DisplayBlackClock(blackTimeRemaining - fudge,
14163                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14164     }
14165
14166     if (CheckFlags()) return;
14167
14168     tickStartTM = now;
14169     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14170     StartClockTimer(intendedTickLength);
14171
14172     /* if the time remaining has fallen below the alarm threshold, sound the
14173      * alarm. if the alarm has sounded and (due to a takeback or time control
14174      * with increment) the time remaining has increased to a level above the
14175      * threshold, reset the alarm so it can sound again.
14176      */
14177
14178     if (appData.icsActive && appData.icsAlarm) {
14179
14180         /* make sure we are dealing with the user's clock */
14181         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14182                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14183            )) return;
14184
14185         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14186             alarmSounded = FALSE;
14187         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14188             PlayAlarmSound();
14189             alarmSounded = TRUE;
14190         }
14191     }
14192 }
14193
14194
14195 /* A player has just moved, so stop the previously running
14196    clock and (if in clock mode) start the other one.
14197    We redisplay both clocks in case we're in ICS mode, because
14198    ICS gives us an update to both clocks after every move.
14199    Note that this routine is called *after* forwardMostMove
14200    is updated, so the last fractional tick must be subtracted
14201    from the color that is *not* on move now.
14202 */
14203 void
14204 SwitchClocks()
14205 {
14206     long lastTickLength;
14207     TimeMark now;
14208     int flagged = FALSE;
14209
14210     GetTimeMark(&now);
14211
14212     if (StopClockTimer() && appData.clockMode) {
14213         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14214         if (WhiteOnMove(forwardMostMove)) {
14215             if(blackNPS >= 0) lastTickLength = 0;
14216             blackTimeRemaining -= lastTickLength;
14217            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14218 //         if(pvInfoList[forwardMostMove-1].time == -1)
14219                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14220                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14221         } else {
14222            if(whiteNPS >= 0) lastTickLength = 0;
14223            whiteTimeRemaining -= lastTickLength;
14224            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14225 //         if(pvInfoList[forwardMostMove-1].time == -1)
14226                  pvInfoList[forwardMostMove-1].time =
14227                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14228         }
14229         flagged = CheckFlags();
14230     }
14231     CheckTimeControl();
14232
14233     if (flagged || !appData.clockMode) return;
14234
14235     switch (gameMode) {
14236       case MachinePlaysBlack:
14237       case MachinePlaysWhite:
14238       case BeginningOfGame:
14239         if (pausing) return;
14240         break;
14241
14242       case EditGame:
14243       case PlayFromGameFile:
14244       case IcsExamining:
14245         return;
14246
14247       default:
14248         break;
14249     }
14250
14251     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14252         if(WhiteOnMove(forwardMostMove))
14253              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14254         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14255     }
14256
14257     tickStartTM = now;
14258     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14259       whiteTimeRemaining : blackTimeRemaining);
14260     StartClockTimer(intendedTickLength);
14261 }
14262
14263
14264 /* Stop both clocks */
14265 void
14266 StopClocks()
14267 {
14268     long lastTickLength;
14269     TimeMark now;
14270
14271     if (!StopClockTimer()) return;
14272     if (!appData.clockMode) return;
14273
14274     GetTimeMark(&now);
14275
14276     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14277     if (WhiteOnMove(forwardMostMove)) {
14278         if(whiteNPS >= 0) lastTickLength = 0;
14279         whiteTimeRemaining -= lastTickLength;
14280         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14281     } else {
14282         if(blackNPS >= 0) lastTickLength = 0;
14283         blackTimeRemaining -= lastTickLength;
14284         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14285     }
14286     CheckFlags();
14287 }
14288
14289 /* Start clock of player on move.  Time may have been reset, so
14290    if clock is already running, stop and restart it. */
14291 void
14292 StartClocks()
14293 {
14294     (void) StopClockTimer(); /* in case it was running already */
14295     DisplayBothClocks();
14296     if (CheckFlags()) return;
14297
14298     if (!appData.clockMode) return;
14299     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14300
14301     GetTimeMark(&tickStartTM);
14302     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14303       whiteTimeRemaining : blackTimeRemaining);
14304
14305    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14306     whiteNPS = blackNPS = -1;
14307     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14308        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14309         whiteNPS = first.nps;
14310     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14311        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14312         blackNPS = first.nps;
14313     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14314         whiteNPS = second.nps;
14315     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14316         blackNPS = second.nps;
14317     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14318
14319     StartClockTimer(intendedTickLength);
14320 }
14321
14322 char *
14323 TimeString(ms)
14324      long ms;
14325 {
14326     long second, minute, hour, day;
14327     char *sign = "";
14328     static char buf[32];
14329
14330     if (ms > 0 && ms <= 9900) {
14331       /* convert milliseconds to tenths, rounding up */
14332       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14333
14334       sprintf(buf, " %03.1f ", tenths/10.0);
14335       return buf;
14336     }
14337
14338     /* convert milliseconds to seconds, rounding up */
14339     /* use floating point to avoid strangeness of integer division
14340        with negative dividends on many machines */
14341     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14342
14343     if (second < 0) {
14344         sign = "-";
14345         second = -second;
14346     }
14347
14348     day = second / (60 * 60 * 24);
14349     second = second % (60 * 60 * 24);
14350     hour = second / (60 * 60);
14351     second = second % (60 * 60);
14352     minute = second / 60;
14353     second = second % 60;
14354
14355     if (day > 0)
14356       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14357               sign, day, hour, minute, second);
14358     else if (hour > 0)
14359       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14360     else
14361       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14362
14363     return buf;
14364 }
14365
14366
14367 /*
14368  * This is necessary because some C libraries aren't ANSI C compliant yet.
14369  */
14370 char *
14371 StrStr(string, match)
14372      char *string, *match;
14373 {
14374     int i, length;
14375
14376     length = strlen(match);
14377
14378     for (i = strlen(string) - length; i >= 0; i--, string++)
14379       if (!strncmp(match, string, length))
14380         return string;
14381
14382     return NULL;
14383 }
14384
14385 char *
14386 StrCaseStr(string, match)
14387      char *string, *match;
14388 {
14389     int i, j, length;
14390
14391     length = strlen(match);
14392
14393     for (i = strlen(string) - length; i >= 0; i--, string++) {
14394         for (j = 0; j < length; j++) {
14395             if (ToLower(match[j]) != ToLower(string[j]))
14396               break;
14397         }
14398         if (j == length) return string;
14399     }
14400
14401     return NULL;
14402 }
14403
14404 #ifndef _amigados
14405 int
14406 StrCaseCmp(s1, s2)
14407      char *s1, *s2;
14408 {
14409     char c1, c2;
14410
14411     for (;;) {
14412         c1 = ToLower(*s1++);
14413         c2 = ToLower(*s2++);
14414         if (c1 > c2) return 1;
14415         if (c1 < c2) return -1;
14416         if (c1 == NULLCHAR) return 0;
14417     }
14418 }
14419
14420
14421 int
14422 ToLower(c)
14423      int c;
14424 {
14425     return isupper(c) ? tolower(c) : c;
14426 }
14427
14428
14429 int
14430 ToUpper(c)
14431      int c;
14432 {
14433     return islower(c) ? toupper(c) : c;
14434 }
14435 #endif /* !_amigados    */
14436
14437 char *
14438 StrSave(s)
14439      char *s;
14440 {
14441     char *ret;
14442
14443     if ((ret = (char *) malloc(strlen(s) + 1))) {
14444         strcpy(ret, s);
14445     }
14446     return ret;
14447 }
14448
14449 char *
14450 StrSavePtr(s, savePtr)
14451      char *s, **savePtr;
14452 {
14453     if (*savePtr) {
14454         free(*savePtr);
14455     }
14456     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14457         strcpy(*savePtr, s);
14458     }
14459     return(*savePtr);
14460 }
14461
14462 char *
14463 PGNDate()
14464 {
14465     time_t clock;
14466     struct tm *tm;
14467     char buf[MSG_SIZ];
14468
14469     clock = time((time_t *)NULL);
14470     tm = localtime(&clock);
14471     sprintf(buf, "%04d.%02d.%02d",
14472             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14473     return StrSave(buf);
14474 }
14475
14476
14477 char *
14478 PositionToFEN(move, overrideCastling)
14479      int move;
14480      char *overrideCastling;
14481 {
14482     int i, j, fromX, fromY, toX, toY;
14483     int whiteToPlay;
14484     char buf[128];
14485     char *p, *q;
14486     int emptycount;
14487     ChessSquare piece;
14488
14489     whiteToPlay = (gameMode == EditPosition) ?
14490       !blackPlaysFirst : (move % 2 == 0);
14491     p = buf;
14492
14493     /* Piece placement data */
14494     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14495         emptycount = 0;
14496         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14497             if (boards[move][i][j] == EmptySquare) {
14498                 emptycount++;
14499             } else { ChessSquare piece = boards[move][i][j];
14500                 if (emptycount > 0) {
14501                     if(emptycount<10) /* [HGM] can be >= 10 */
14502                         *p++ = '0' + emptycount;
14503                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14504                     emptycount = 0;
14505                 }
14506                 if(PieceToChar(piece) == '+') {
14507                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14508                     *p++ = '+';
14509                     piece = (ChessSquare)(DEMOTED piece);
14510                 }
14511                 *p++ = PieceToChar(piece);
14512                 if(p[-1] == '~') {
14513                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14514                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14515                     *p++ = '~';
14516                 }
14517             }
14518         }
14519         if (emptycount > 0) {
14520             if(emptycount<10) /* [HGM] can be >= 10 */
14521                 *p++ = '0' + emptycount;
14522             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14523             emptycount = 0;
14524         }
14525         *p++ = '/';
14526     }
14527     *(p - 1) = ' ';
14528
14529     /* [HGM] print Crazyhouse or Shogi holdings */
14530     if( gameInfo.holdingsWidth ) {
14531         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14532         q = p;
14533         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14534             piece = boards[move][i][BOARD_WIDTH-1];
14535             if( piece != EmptySquare )
14536               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14537                   *p++ = PieceToChar(piece);
14538         }
14539         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14540             piece = boards[move][BOARD_HEIGHT-i-1][0];
14541             if( piece != EmptySquare )
14542               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14543                   *p++ = PieceToChar(piece);
14544         }
14545
14546         if( q == p ) *p++ = '-';
14547         *p++ = ']';
14548         *p++ = ' ';
14549     }
14550
14551     /* Active color */
14552     *p++ = whiteToPlay ? 'w' : 'b';
14553     *p++ = ' ';
14554
14555   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14556     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14557   } else {
14558   if(nrCastlingRights) {
14559      q = p;
14560      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14561        /* [HGM] write directly from rights */
14562            if(boards[move][CASTLING][2] != NoRights &&
14563               boards[move][CASTLING][0] != NoRights   )
14564                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14565            if(boards[move][CASTLING][2] != NoRights &&
14566               boards[move][CASTLING][1] != NoRights   )
14567                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14568            if(boards[move][CASTLING][5] != NoRights &&
14569               boards[move][CASTLING][3] != NoRights   )
14570                 *p++ = boards[move][CASTLING][3] + AAA;
14571            if(boards[move][CASTLING][5] != NoRights &&
14572               boards[move][CASTLING][4] != NoRights   )
14573                 *p++ = boards[move][CASTLING][4] + AAA;
14574      } else {
14575
14576         /* [HGM] write true castling rights */
14577         if( nrCastlingRights == 6 ) {
14578             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14579                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14580             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14581                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14582             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14583                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14584             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14585                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14586         }
14587      }
14588      if (q == p) *p++ = '-'; /* No castling rights */
14589      *p++ = ' ';
14590   }
14591
14592   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14593      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14594     /* En passant target square */
14595     if (move > backwardMostMove) {
14596         fromX = moveList[move - 1][0] - AAA;
14597         fromY = moveList[move - 1][1] - ONE;
14598         toX = moveList[move - 1][2] - AAA;
14599         toY = moveList[move - 1][3] - ONE;
14600         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14601             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14602             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14603             fromX == toX) {
14604             /* 2-square pawn move just happened */
14605             *p++ = toX + AAA;
14606             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14607         } else {
14608             *p++ = '-';
14609         }
14610     } else if(move == backwardMostMove) {
14611         // [HGM] perhaps we should always do it like this, and forget the above?
14612         if((signed char)boards[move][EP_STATUS] >= 0) {
14613             *p++ = boards[move][EP_STATUS] + AAA;
14614             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14615         } else {
14616             *p++ = '-';
14617         }
14618     } else {
14619         *p++ = '-';
14620     }
14621     *p++ = ' ';
14622   }
14623   }
14624
14625     /* [HGM] find reversible plies */
14626     {   int i = 0, j=move;
14627
14628         if (appData.debugMode) { int k;
14629             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14630             for(k=backwardMostMove; k<=forwardMostMove; k++)
14631                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14632
14633         }
14634
14635         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14636         if( j == backwardMostMove ) i += initialRulePlies;
14637         sprintf(p, "%d ", i);
14638         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14639     }
14640     /* Fullmove number */
14641     sprintf(p, "%d", (move / 2) + 1);
14642
14643     return StrSave(buf);
14644 }
14645
14646 Boolean
14647 ParseFEN(board, blackPlaysFirst, fen)
14648     Board board;
14649      int *blackPlaysFirst;
14650      char *fen;
14651 {
14652     int i, j;
14653     char *p;
14654     int emptycount;
14655     ChessSquare piece;
14656
14657     p = fen;
14658
14659     /* [HGM] by default clear Crazyhouse holdings, if present */
14660     if(gameInfo.holdingsWidth) {
14661        for(i=0; i<BOARD_HEIGHT; i++) {
14662            board[i][0]             = EmptySquare; /* black holdings */
14663            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14664            board[i][1]             = (ChessSquare) 0; /* black counts */
14665            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14666        }
14667     }
14668
14669     /* Piece placement data */
14670     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14671         j = 0;
14672         for (;;) {
14673             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14674                 if (*p == '/') p++;
14675                 emptycount = gameInfo.boardWidth - j;
14676                 while (emptycount--)
14677                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14678                 break;
14679 #if(BOARD_FILES >= 10)
14680             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14681                 p++; emptycount=10;
14682                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14683                 while (emptycount--)
14684                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14685 #endif
14686             } else if (isdigit(*p)) {
14687                 emptycount = *p++ - '0';
14688                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14689                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14690                 while (emptycount--)
14691                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14692             } else if (*p == '+' || isalpha(*p)) {
14693                 if (j >= gameInfo.boardWidth) return FALSE;
14694                 if(*p=='+') {
14695                     piece = CharToPiece(*++p);
14696                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14697                     piece = (ChessSquare) (PROMOTED piece ); p++;
14698                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14699                 } else piece = CharToPiece(*p++);
14700
14701                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14702                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14703                     piece = (ChessSquare) (PROMOTED piece);
14704                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14705                     p++;
14706                 }
14707                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14708             } else {
14709                 return FALSE;
14710             }
14711         }
14712     }
14713     while (*p == '/' || *p == ' ') p++;
14714
14715     /* [HGM] look for Crazyhouse holdings here */
14716     while(*p==' ') p++;
14717     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14718         if(*p == '[') p++;
14719         if(*p == '-' ) *p++; /* empty holdings */ else {
14720             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14721             /* if we would allow FEN reading to set board size, we would   */
14722             /* have to add holdings and shift the board read so far here   */
14723             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14724                 *p++;
14725                 if((int) piece >= (int) BlackPawn ) {
14726                     i = (int)piece - (int)BlackPawn;
14727                     i = PieceToNumber((ChessSquare)i);
14728                     if( i >= gameInfo.holdingsSize ) return FALSE;
14729                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14730                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14731                 } else {
14732                     i = (int)piece - (int)WhitePawn;
14733                     i = PieceToNumber((ChessSquare)i);
14734                     if( i >= gameInfo.holdingsSize ) return FALSE;
14735                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14736                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14737                 }
14738             }
14739         }
14740         if(*p == ']') *p++;
14741     }
14742
14743     while(*p == ' ') p++;
14744
14745     /* Active color */
14746     switch (*p++) {
14747       case 'w':
14748         *blackPlaysFirst = FALSE;
14749         break;
14750       case 'b':
14751         *blackPlaysFirst = TRUE;
14752         break;
14753       default:
14754         return FALSE;
14755     }
14756
14757     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14758     /* return the extra info in global variiables             */
14759
14760     /* set defaults in case FEN is incomplete */
14761     board[EP_STATUS] = EP_UNKNOWN;
14762     for(i=0; i<nrCastlingRights; i++ ) {
14763         board[CASTLING][i] =
14764             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14765     }   /* assume possible unless obviously impossible */
14766     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14767     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14768     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14769                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14770     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14771     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14772     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14773                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14774     FENrulePlies = 0;
14775
14776     while(*p==' ') p++;
14777     if(nrCastlingRights) {
14778       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14779           /* castling indicator present, so default becomes no castlings */
14780           for(i=0; i<nrCastlingRights; i++ ) {
14781                  board[CASTLING][i] = NoRights;
14782           }
14783       }
14784       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14785              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14786              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14787              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14788         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14789
14790         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14791             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14792             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14793         }
14794         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14795             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14796         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14797                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14798         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14799                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14800         switch(c) {
14801           case'K':
14802               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14803               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14804               board[CASTLING][2] = whiteKingFile;
14805               break;
14806           case'Q':
14807               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14808               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14809               board[CASTLING][2] = whiteKingFile;
14810               break;
14811           case'k':
14812               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14813               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14814               board[CASTLING][5] = blackKingFile;
14815               break;
14816           case'q':
14817               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14818               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14819               board[CASTLING][5] = blackKingFile;
14820           case '-':
14821               break;
14822           default: /* FRC castlings */
14823               if(c >= 'a') { /* black rights */
14824                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14825                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14826                   if(i == BOARD_RGHT) break;
14827                   board[CASTLING][5] = i;
14828                   c -= AAA;
14829                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14830                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14831                   if(c > i)
14832                       board[CASTLING][3] = c;
14833                   else
14834                       board[CASTLING][4] = c;
14835               } else { /* white rights */
14836                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14837                     if(board[0][i] == WhiteKing) break;
14838                   if(i == BOARD_RGHT) break;
14839                   board[CASTLING][2] = i;
14840                   c -= AAA - 'a' + 'A';
14841                   if(board[0][c] >= WhiteKing) break;
14842                   if(c > i)
14843                       board[CASTLING][0] = c;
14844                   else
14845                       board[CASTLING][1] = c;
14846               }
14847         }
14848       }
14849       for(i=0; i<nrCastlingRights; i++)
14850         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14851     if (appData.debugMode) {
14852         fprintf(debugFP, "FEN castling rights:");
14853         for(i=0; i<nrCastlingRights; i++)
14854         fprintf(debugFP, " %d", board[CASTLING][i]);
14855         fprintf(debugFP, "\n");
14856     }
14857
14858       while(*p==' ') p++;
14859     }
14860
14861     /* read e.p. field in games that know e.p. capture */
14862     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14863        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14864       if(*p=='-') {
14865         p++; board[EP_STATUS] = EP_NONE;
14866       } else {
14867          char c = *p++ - AAA;
14868
14869          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14870          if(*p >= '0' && *p <='9') *p++;
14871          board[EP_STATUS] = c;
14872       }
14873     }
14874
14875
14876     if(sscanf(p, "%d", &i) == 1) {
14877         FENrulePlies = i; /* 50-move ply counter */
14878         /* (The move number is still ignored)    */
14879     }
14880
14881     return TRUE;
14882 }
14883
14884 void
14885 EditPositionPasteFEN(char *fen)
14886 {
14887   if (fen != NULL) {
14888     Board initial_position;
14889
14890     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14891       DisplayError(_("Bad FEN position in clipboard"), 0);
14892       return ;
14893     } else {
14894       int savedBlackPlaysFirst = blackPlaysFirst;
14895       EditPositionEvent();
14896       blackPlaysFirst = savedBlackPlaysFirst;
14897       CopyBoard(boards[0], initial_position);
14898       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14899       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14900       DisplayBothClocks();
14901       DrawPosition(FALSE, boards[currentMove]);
14902     }
14903   }
14904 }
14905
14906 static char cseq[12] = "\\   ";
14907
14908 Boolean set_cont_sequence(char *new_seq)
14909 {
14910     int len;
14911     Boolean ret;
14912
14913     // handle bad attempts to set the sequence
14914         if (!new_seq)
14915                 return 0; // acceptable error - no debug
14916
14917     len = strlen(new_seq);
14918     ret = (len > 0) && (len < sizeof(cseq));
14919     if (ret)
14920         strcpy(cseq, new_seq);
14921     else if (appData.debugMode)
14922         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14923     return ret;
14924 }
14925
14926 /*
14927     reformat a source message so words don't cross the width boundary.  internal
14928     newlines are not removed.  returns the wrapped size (no null character unless
14929     included in source message).  If dest is NULL, only calculate the size required
14930     for the dest buffer.  lp argument indicats line position upon entry, and it's
14931     passed back upon exit.
14932 */
14933 int wrap(char *dest, char *src, int count, int width, int *lp)
14934 {
14935     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14936
14937     cseq_len = strlen(cseq);
14938     old_line = line = *lp;
14939     ansi = len = clen = 0;
14940
14941     for (i=0; i < count; i++)
14942     {
14943         if (src[i] == '\033')
14944             ansi = 1;
14945
14946         // if we hit the width, back up
14947         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14948         {
14949             // store i & len in case the word is too long
14950             old_i = i, old_len = len;
14951
14952             // find the end of the last word
14953             while (i && src[i] != ' ' && src[i] != '\n')
14954             {
14955                 i--;
14956                 len--;
14957             }
14958
14959             // word too long?  restore i & len before splitting it
14960             if ((old_i-i+clen) >= width)
14961             {
14962                 i = old_i;
14963                 len = old_len;
14964             }
14965
14966             // extra space?
14967             if (i && src[i-1] == ' ')
14968                 len--;
14969
14970             if (src[i] != ' ' && src[i] != '\n')
14971             {
14972                 i--;
14973                 if (len)
14974                     len--;
14975             }
14976
14977             // now append the newline and continuation sequence
14978             if (dest)
14979                 dest[len] = '\n';
14980             len++;
14981             if (dest)
14982                 strncpy(dest+len, cseq, cseq_len);
14983             len += cseq_len;
14984             line = cseq_len;
14985             clen = cseq_len;
14986             continue;
14987         }
14988
14989         if (dest)
14990             dest[len] = src[i];
14991         len++;
14992         if (!ansi)
14993             line++;
14994         if (src[i] == '\n')
14995             line = 0;
14996         if (src[i] == 'm')
14997             ansi = 0;
14998     }
14999     if (dest && appData.debugMode)
15000     {
15001         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15002             count, width, line, len, *lp);
15003         show_bytes(debugFP, src, count);
15004         fprintf(debugFP, "\ndest: ");
15005         show_bytes(debugFP, dest, len);
15006         fprintf(debugFP, "\n");
15007     }
15008     *lp = dest ? line : old_line;
15009
15010     return len;
15011 }
15012
15013 // [HGM] vari: routines for shelving variations
15014
15015 void 
15016 PushTail(int firstMove, int lastMove)
15017 {
15018         int i, j, nrMoves = lastMove - firstMove;
15019
15020         if(appData.icsActive) { // only in local mode
15021                 forwardMostMove = currentMove; // mimic old ICS behavior
15022                 return;
15023         }
15024         if(storedGames >= MAX_VARIATIONS-1) return;
15025
15026         // push current tail of game on stack
15027         savedResult[storedGames] = gameInfo.result;
15028         savedDetails[storedGames] = gameInfo.resultDetails;
15029         gameInfo.resultDetails = NULL;
15030         savedFirst[storedGames] = firstMove;
15031         savedLast [storedGames] = lastMove;
15032         savedFramePtr[storedGames] = framePtr;
15033         framePtr -= nrMoves; // reserve space for the boards
15034         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15035             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15036             for(j=0; j<MOVE_LEN; j++)
15037                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15038             for(j=0; j<2*MOVE_LEN; j++)
15039                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15040             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15041             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15042             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15043             pvInfoList[firstMove+i-1].depth = 0;
15044             commentList[framePtr+i] = commentList[firstMove+i];
15045             commentList[firstMove+i] = NULL;
15046         }
15047
15048         storedGames++;
15049         forwardMostMove = currentMove; // truncte game so we can start variation
15050         if(storedGames == 1) GreyRevert(FALSE);
15051 }
15052
15053 Boolean
15054 PopTail(Boolean annotate)
15055 {
15056         int i, j, nrMoves;
15057         char buf[8000], moveBuf[20];
15058
15059         if(appData.icsActive) return FALSE; // only in local mode
15060         if(!storedGames) return FALSE; // sanity
15061
15062         storedGames--;
15063         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15064         nrMoves = savedLast[storedGames] - currentMove;
15065         if(annotate) {
15066                 int cnt = 10;
15067                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15068                 else strcpy(buf, "(");
15069                 for(i=currentMove; i<forwardMostMove; i++) {
15070                         if(WhiteOnMove(i))
15071                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15072                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15073                         strcat(buf, moveBuf);
15074                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15075                 }
15076                 strcat(buf, ")");
15077         }
15078         for(i=1; i<nrMoves; i++) { // copy last variation back
15079             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15080             for(j=0; j<MOVE_LEN; j++)
15081                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15082             for(j=0; j<2*MOVE_LEN; j++)
15083                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15084             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15085             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15086             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15087             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15088             commentList[currentMove+i] = commentList[framePtr+i];
15089             commentList[framePtr+i] = NULL;
15090         }
15091         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15092         framePtr = savedFramePtr[storedGames];
15093         gameInfo.result = savedResult[storedGames];
15094         if(gameInfo.resultDetails != NULL) {
15095             free(gameInfo.resultDetails);
15096       }
15097         gameInfo.resultDetails = savedDetails[storedGames];
15098         forwardMostMove = currentMove + nrMoves;
15099         if(storedGames == 0) GreyRevert(TRUE);
15100         return TRUE;
15101 }
15102
15103 void 
15104 CleanupTail()
15105 {       // remove all shelved variations
15106         int i;
15107         for(i=0; i<storedGames; i++) {
15108             if(savedDetails[i])
15109                 free(savedDetails[i]);
15110             savedDetails[i] = NULL;
15111         }
15112         for(i=framePtr; i<MAX_MOVES; i++) {
15113                 if(commentList[i]) free(commentList[i]);
15114                 commentList[i] = NULL;
15115         }
15116         framePtr = MAX_MOVES-1;
15117         storedGames = 0;
15118 }