Merge commit 'master-20091122' into gtk
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 #else
135 # define _(s) (s)
136 # define N_(s) s
137 #endif
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom:
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /*
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
445 signed char  initialRights[BOARD_FILES];
446 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int   initialRulePlies, FENrulePlies;
448 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
449 int loadFlag = 0;
450 int shuffleOpenings;
451 int mute; // mute all sounds
452
453 // [HGM] vari: next 12 to save and restore variations
454 #define MAX_VARIATIONS 10
455 int framePtr = MAX_MOVES-1; // points to free stack entry
456 int storedGames = 0;
457 int savedFirst[MAX_VARIATIONS];
458 int savedLast[MAX_VARIATIONS];
459 int savedFramePtr[MAX_VARIATIONS];
460 char *savedDetails[MAX_VARIATIONS];
461 ChessMove savedResult[MAX_VARIATIONS];
462
463 void PushTail P((int firstMove, int lastMove));
464 Boolean PopTail P((Boolean annotate));
465 void CleanupTail P((void));
466
467 ChessSquare  FIDEArray[2][BOARD_FILES] = {
468     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
469         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
470     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
471         BlackKing, BlackBishop, BlackKnight, BlackRook }
472 };
473
474 ChessSquare twoKingsArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackKing, BlackKnight, BlackRook }
479 };
480
481 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
483         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
484     { BlackRook, BlackMan, BlackBishop, BlackQueen,
485         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
486 };
487
488 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
489     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
490         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
491     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
492         BlackKing, BlackBishop, BlackKnight, BlackRook }
493 };
494
495 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
496     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
497         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
498     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
499         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
500 };
501
502
503 #if (BOARD_FILES>=10)
504 ChessSquare ShogiArray[2][BOARD_FILES] = {
505     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
506         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
507     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
508         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
509 };
510
511 ChessSquare XiangqiArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
513         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
515         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
516 };
517
518 ChessSquare CapablancaArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
520         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
522         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
523 };
524
525 ChessSquare GreatArray[2][BOARD_FILES] = {
526     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
527         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
528     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
529         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
530 };
531
532 ChessSquare JanusArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
534         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
535     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
536         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
537 };
538
539 #ifdef GOTHIC
540 ChessSquare GothicArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
542         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
544         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !GOTHIC
547 #define GothicArray CapablancaArray
548 #endif // !GOTHIC
549
550 #ifdef FALCON
551 ChessSquare FalconArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
553         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
555         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
556 };
557 #else // !FALCON
558 #define FalconArray CapablancaArray
559 #endif // !FALCON
560
561 #else // !(BOARD_FILES>=10)
562 #define XiangqiPosition FIDEArray
563 #define CapablancaArray FIDEArray
564 #define GothicArray FIDEArray
565 #define GreatArray FIDEArray
566 #endif // !(BOARD_FILES>=10)
567
568 #if (BOARD_FILES>=12)
569 ChessSquare CourierArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
571         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
573         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
574 };
575 #else // !(BOARD_FILES>=12)
576 #define CourierArray CapablancaArray
577 #endif // !(BOARD_FILES>=12)
578
579
580 Board initialPosition;
581
582
583 /* Convert str to a rating. Checks for special cases of "----",
584
585    "++++", etc. Also strips ()'s */
586 int
587 string_to_rating(str)
588   char *str;
589 {
590   while(*str && !isdigit(*str)) ++str;
591   if (!*str)
592     return 0;   /* One of the special "no rating" cases */
593   else
594     return atoi(str);
595 }
596
597 void
598 ClearProgramStats()
599 {
600     /* Init programStats */
601     programStats.movelist[0] = 0;
602     programStats.depth = 0;
603     programStats.nr_moves = 0;
604     programStats.moves_left = 0;
605     programStats.nodes = 0;
606     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
607     programStats.score = 0;
608     programStats.got_only_move = 0;
609     programStats.got_fail = 0;
610     programStats.line_is_book = 0;
611 }
612
613 void
614 InitBackEnd1()
615 {
616     int matched, min, sec;
617
618     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
619
620     GetTimeMark(&programStartTime);
621     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
622
623     ClearProgramStats();
624     programStats.ok_to_send = 1;
625     programStats.seen_stat = 0;
626
627     /*
628      * Initialize game list
629      */
630     ListNew(&gameList);
631
632
633     /*
634      * Internet chess server status
635      */
636     if (appData.icsActive) {
637         appData.matchMode = FALSE;
638         appData.matchGames = 0;
639 #if ZIPPY
640         appData.noChessProgram = !appData.zippyPlay;
641 #else
642         appData.zippyPlay = FALSE;
643         appData.zippyTalk = FALSE;
644         appData.noChessProgram = TRUE;
645 #endif
646         if (*appData.icsHelper != NULLCHAR) {
647             appData.useTelnet = TRUE;
648             appData.telnetProgram = appData.icsHelper;
649         }
650     } else {
651         appData.zippyTalk = appData.zippyPlay = FALSE;
652     }
653
654     /* [AS] Initialize pv info list [HGM] and game state */
655     {
656         int i, j;
657
658         for( i=0; i<=framePtr; i++ ) {
659             pvInfoList[i].depth = -1;
660             boards[i][EP_STATUS] = EP_NONE;
661             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
662         }
663     }
664
665     /*
666      * Parse timeControl resource
667      */
668     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
669                           appData.movesPerSession)) {
670         char buf[MSG_SIZ];
671         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
672         DisplayFatalError(buf, 0, 2);
673     }
674
675     /*
676      * Parse searchTime resource
677      */
678     if (*appData.searchTime != NULLCHAR) {
679         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
680         if (matched == 1) {
681             searchTime = min * 60;
682         } else if (matched == 2) {
683             searchTime = min * 60 + sec;
684         } else {
685             char buf[MSG_SIZ];
686             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
687             DisplayFatalError(buf, 0, 2);
688         }
689     }
690
691     /* [AS] Adjudication threshold */
692     adjudicateLossThreshold = appData.adjudicateLossThreshold;
693
694     first.which = "first";
695     second.which = "second";
696     first.maybeThinking = second.maybeThinking = FALSE;
697     first.pr = second.pr = NoProc;
698     first.isr = second.isr = NULL;
699     first.sendTime = second.sendTime = 2;
700     first.sendDrawOffers = 1;
701     if (appData.firstPlaysBlack) {
702         first.twoMachinesColor = "black\n";
703         second.twoMachinesColor = "white\n";
704     } else {
705         first.twoMachinesColor = "white\n";
706         second.twoMachinesColor = "black\n";
707     }
708     first.program = appData.firstChessProgram;
709     second.program = appData.secondChessProgram;
710     first.host = appData.firstHost;
711     second.host = appData.secondHost;
712     first.dir = appData.firstDirectory;
713     second.dir = appData.secondDirectory;
714     first.other = &second;
715     second.other = &first;
716     first.initString = appData.initString;
717     second.initString = appData.secondInitString;
718     first.computerString = appData.firstComputerString;
719     second.computerString = appData.secondComputerString;
720     first.useSigint = second.useSigint = TRUE;
721     first.useSigterm = second.useSigterm = TRUE;
722     first.reuse = appData.reuseFirst;
723     second.reuse = appData.reuseSecond;
724     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
725     second.nps = appData.secondNPS;
726     first.useSetboard = second.useSetboard = FALSE;
727     first.useSAN = second.useSAN = FALSE;
728     first.usePing = second.usePing = FALSE;
729     first.lastPing = second.lastPing = 0;
730     first.lastPong = second.lastPong = 0;
731     first.usePlayother = second.usePlayother = FALSE;
732     first.useColors = second.useColors = TRUE;
733     first.useUsermove = second.useUsermove = FALSE;
734     first.sendICS = second.sendICS = FALSE;
735     first.sendName = second.sendName = appData.icsActive;
736     first.sdKludge = second.sdKludge = FALSE;
737     first.stKludge = second.stKludge = FALSE;
738     TidyProgramName(first.program, first.host, first.tidy);
739     TidyProgramName(second.program, second.host, second.tidy);
740     first.matchWins = second.matchWins = 0;
741     strcpy(first.variants, appData.variant);
742     strcpy(second.variants, appData.variant);
743     first.analysisSupport = second.analysisSupport = 2; /* detect */
744     first.analyzing = second.analyzing = FALSE;
745     first.initDone = second.initDone = FALSE;
746
747     /* New features added by Tord: */
748     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
749     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
750     /* End of new features added by Tord. */
751     first.fenOverride  = appData.fenOverride1;
752     second.fenOverride = appData.fenOverride2;
753
754     /* [HGM] time odds: set factor for each machine */
755     first.timeOdds  = appData.firstTimeOdds;
756     second.timeOdds = appData.secondTimeOdds;
757     { int norm = 1;
758         if(appData.timeOddsMode) {
759             norm = first.timeOdds;
760             if(norm > second.timeOdds) norm = second.timeOdds;
761         }
762         first.timeOdds /= norm;
763         second.timeOdds /= norm;
764     }
765
766     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
767     first.accumulateTC = appData.firstAccumulateTC;
768     second.accumulateTC = appData.secondAccumulateTC;
769     first.maxNrOfSessions = second.maxNrOfSessions = 1;
770
771     /* [HGM] debug */
772     first.debug = second.debug = FALSE;
773     first.supportsNPS = second.supportsNPS = UNKNOWN;
774
775     /* [HGM] options */
776     first.optionSettings  = appData.firstOptions;
777     second.optionSettings = appData.secondOptions;
778
779     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
780     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
781     first.isUCI = appData.firstIsUCI; /* [AS] */
782     second.isUCI = appData.secondIsUCI; /* [AS] */
783     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
784     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
785
786     if (appData.firstProtocolVersion > PROTOVER ||
787         appData.firstProtocolVersion < 1) {
788       char buf[MSG_SIZ];
789       sprintf(buf, _("protocol version %d not supported"),
790               appData.firstProtocolVersion);
791       DisplayFatalError(buf, 0, 2);
792     } else {
793       first.protocolVersion = appData.firstProtocolVersion;
794     }
795
796     if (appData.secondProtocolVersion > PROTOVER ||
797         appData.secondProtocolVersion < 1) {
798       char buf[MSG_SIZ];
799       sprintf(buf, _("protocol version %d not supported"),
800               appData.secondProtocolVersion);
801       DisplayFatalError(buf, 0, 2);
802     } else {
803       second.protocolVersion = appData.secondProtocolVersion;
804     }
805
806     if (appData.icsActive) {
807         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
808 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
809     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
810         appData.clockMode = FALSE;
811         first.sendTime = second.sendTime = 0;
812     }
813
814 #if ZIPPY
815     /* Override some settings from environment variables, for backward
816        compatibility.  Unfortunately it's not feasible to have the env
817        vars just set defaults, at least in xboard.  Ugh.
818     */
819     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
820       ZippyInit();
821     }
822 #endif
823
824     if (appData.noChessProgram) {
825         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
826         sprintf(programVersion, "%s", PACKAGE_STRING);
827     } else {
828       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
829       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
830       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
831     }
832
833     if (!appData.icsActive) {
834       char buf[MSG_SIZ];
835       /* Check for variants that are supported only in ICS mode,
836          or not at all.  Some that are accepted here nevertheless
837          have bugs; see comments below.
838       */
839       VariantClass variant = StringToVariant(appData.variant);
840       switch (variant) {
841       case VariantBughouse:     /* need four players and two boards */
842       case VariantKriegspiel:   /* need to hide pieces and move details */
843       /* case VariantFischeRandom: (Fabien: moved below) */
844         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
845         DisplayFatalError(buf, 0, 2);
846         return;
847
848       case VariantUnknown:
849       case VariantLoadable:
850       case Variant29:
851       case Variant30:
852       case Variant31:
853       case Variant32:
854       case Variant33:
855       case Variant34:
856       case Variant35:
857       case Variant36:
858       default:
859         sprintf(buf, _("Unknown variant name %s"), appData.variant);
860         DisplayFatalError(buf, 0, 2);
861         return;
862
863       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
864       case VariantFairy:      /* [HGM] TestLegality definitely off! */
865       case VariantGothic:     /* [HGM] should work */
866       case VariantCapablanca: /* [HGM] should work */
867       case VariantCourier:    /* [HGM] initial forced moves not implemented */
868       case VariantShogi:      /* [HGM] drops not tested for legality */
869       case VariantKnightmate: /* [HGM] should work */
870       case VariantCylinder:   /* [HGM] untested */
871       case VariantFalcon:     /* [HGM] untested */
872       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
873                                  offboard interposition not understood */
874       case VariantNormal:     /* definitely works! */
875       case VariantWildCastle: /* pieces not automatically shuffled */
876       case VariantNoCastle:   /* pieces not automatically shuffled */
877       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
878       case VariantLosers:     /* should work except for win condition,
879                                  and doesn't know captures are mandatory */
880       case VariantSuicide:    /* should work except for win condition,
881                                  and doesn't know captures are mandatory */
882       case VariantGiveaway:   /* should work except for win condition,
883                                  and doesn't know captures are mandatory */
884       case VariantTwoKings:   /* should work */
885       case VariantAtomic:     /* should work except for win condition */
886       case Variant3Check:     /* should work except for win condition */
887       case VariantShatranj:   /* should work except for all win conditions */
888       case VariantBerolina:   /* might work if TestLegality is off */
889       case VariantCapaRandom: /* should work */
890       case VariantJanus:      /* should work */
891       case VariantSuper:      /* experimental */
892       case VariantGreat:      /* experimental, requires legality testing to be off */
893         break;
894       }
895     }
896
897     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
898     InitEngineUCI( installDir, &second );
899 }
900
901 int NextIntegerFromString( char ** str, long * value )
902 {
903     int result = -1;
904     char * s = *str;
905
906     while( *s == ' ' || *s == '\t' ) {
907         s++;
908     }
909
910     *value = 0;
911
912     if( *s >= '0' && *s <= '9' ) {
913         while( *s >= '0' && *s <= '9' ) {
914             *value = *value * 10 + (*s - '0');
915             s++;
916         }
917
918         result = 0;
919     }
920
921     *str = s;
922
923     return result;
924 }
925
926 int NextTimeControlFromString( char ** str, long * value )
927 {
928     long temp;
929     int result = NextIntegerFromString( str, &temp );
930
931     if( result == 0 ) {
932         *value = temp * 60; /* Minutes */
933         if( **str == ':' ) {
934             (*str)++;
935             result = NextIntegerFromString( str, &temp );
936             *value += temp; /* Seconds */
937         }
938     }
939
940     return result;
941 }
942
943 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
944 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
945     int result = -1; long temp, temp2;
946
947     if(**str != '+') return -1; // old params remain in force!
948     (*str)++;
949     if( NextTimeControlFromString( str, &temp ) ) return -1;
950
951     if(**str != '/') {
952         /* time only: incremental or sudden-death time control */
953         if(**str == '+') { /* increment follows; read it */
954             (*str)++;
955             if(result = NextIntegerFromString( str, &temp2)) return -1;
956             *inc = temp2 * 1000;
957         } else *inc = 0;
958         *moves = 0; *tc = temp * 1000;
959         return 0;
960     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
961
962     (*str)++; /* classical time control */
963     result = NextTimeControlFromString( str, &temp2);
964     if(result == 0) {
965         *moves = temp/60;
966         *tc    = temp2 * 1000;
967         *inc   = 0;
968     }
969     return result;
970 }
971
972 int GetTimeQuota(int movenr)
973 {   /* [HGM] get time to add from the multi-session time-control string */
974     int moves=1; /* kludge to force reading of first session */
975     long time, increment;
976     char *s = fullTimeControlString;
977
978     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
979     do {
980         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
981         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
982         if(movenr == -1) return time;    /* last move before new session     */
983         if(!moves) return increment;     /* current session is incremental   */
984         if(movenr >= 0) movenr -= moves; /* we already finished this session */
985     } while(movenr >= -1);               /* try again for next session       */
986
987     return 0; // no new time quota on this move
988 }
989
990 int
991 ParseTimeControl(tc, ti, mps)
992      char *tc;
993      int ti;
994      int mps;
995 {
996   long tc1;
997   long tc2;
998   char buf[MSG_SIZ];
999   
1000   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1001   if(ti > 0) {
1002     if(mps)
1003       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1004     else sprintf(buf, "+%s+%d", tc, ti);
1005   } else {
1006     if(mps)
1007              sprintf(buf, "+%d/%s", mps, tc);
1008     else sprintf(buf, "+%s", tc);
1009   }
1010   fullTimeControlString = StrSave(buf);
1011   
1012   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1013     return FALSE;
1014   }
1015   
1016   if( *tc == '/' ) {
1017     /* Parse second time control */
1018     tc++;
1019     
1020     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1021       return FALSE;
1022     }
1023     
1024     if( tc2 == 0 ) {
1025       return FALSE;
1026     }
1027     
1028     timeControl_2 = tc2 * 1000;
1029   }
1030   else {
1031     timeControl_2 = 0;
1032   }
1033   
1034   if( tc1 == 0 ) {
1035     return FALSE;
1036   }
1037   
1038   timeControl = tc1 * 1000;
1039   
1040   if (ti >= 0) {
1041     timeIncrement = ti * 1000;  /* convert to ms */
1042     movesPerSession = 0;
1043   } else {
1044     timeIncrement = 0;
1045     movesPerSession = mps;
1046   }
1047   return TRUE;
1048 }
1049
1050 void
1051 InitBackEnd2()
1052 {
1053   if (appData.debugMode) {
1054     fprintf(debugFP, "%s\n", programVersion);
1055   }
1056
1057   set_cont_sequence(appData.wrapContSeq);
1058   if (appData.matchGames > 0) {
1059     appData.matchMode = TRUE;
1060   } else if (appData.matchMode) {
1061     appData.matchGames = 1;
1062   }
1063   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1064     appData.matchGames = appData.sameColorGames;
1065   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1066     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1067     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1068   }
1069   Reset(TRUE, FALSE);
1070   if (appData.noChessProgram || first.protocolVersion == 1) {
1071     InitBackEnd3();
1072     } else {
1073     /* kludge: allow timeout for initial "feature" commands */
1074     FreezeUI();
1075     DisplayMessage("", _("Starting chess program"));
1076     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1077   }
1078 }
1079
1080 void
1081 InitBackEnd3 P((void))
1082 {
1083     GameMode initialMode;
1084     char buf[MSG_SIZ];
1085     int err;
1086
1087     InitChessProgram(&first, startedFromSetupPosition);
1088
1089
1090     if (appData.icsActive) {
1091 #ifdef WIN32
1092         /* [DM] Make a console window if needed [HGM] merged ifs */
1093         ConsoleCreate();
1094 #endif
1095         err = establish();
1096         if (err != 0) {
1097             if (*appData.icsCommPort != NULLCHAR) {
1098                 sprintf(buf, _("Could not open comm port %s"),
1099                         appData.icsCommPort);
1100             } else {
1101                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1102                         appData.icsHost, appData.icsPort);
1103             }
1104             DisplayFatalError(buf, err, 1);
1105             return;
1106         }
1107         SetICSMode();
1108         telnetISR =
1109           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1110         fromUserISR =
1111           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1112     } else if (appData.noChessProgram) {
1113         SetNCPMode();
1114     } else {
1115         SetGNUMode();
1116     }
1117
1118     if (*appData.cmailGameName != NULLCHAR) {
1119         SetCmailMode();
1120         OpenLoopback(&cmailPR);
1121         cmailISR =
1122           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1123     }
1124
1125     ThawUI();
1126     DisplayMessage("", "");
1127     if (StrCaseCmp(appData.initialMode, "") == 0) {
1128       initialMode = BeginningOfGame;
1129     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1130       initialMode = TwoMachinesPlay;
1131     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1132       initialMode = AnalyzeFile;
1133     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1134       initialMode = AnalyzeMode;
1135     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1136       initialMode = MachinePlaysWhite;
1137     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1138       initialMode = MachinePlaysBlack;
1139     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1140       initialMode = EditGame;
1141     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1142       initialMode = EditPosition;
1143     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1144       initialMode = Training;
1145     } else {
1146       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1147       DisplayFatalError(buf, 0, 2);
1148       return;
1149     }
1150
1151     if (appData.matchMode) {
1152         /* Set up machine vs. machine match */
1153         if (appData.noChessProgram) {
1154             DisplayFatalError(_("Can't have a match with no chess programs"),
1155                               0, 2);
1156             return;
1157         }
1158         matchMode = TRUE;
1159         matchGame = 1;
1160         if (*appData.loadGameFile != NULLCHAR) {
1161             int index = appData.loadGameIndex; // [HGM] autoinc
1162             if(index<0) lastIndex = index = 1;
1163             if (!LoadGameFromFile(appData.loadGameFile,
1164                                   index,
1165                                   appData.loadGameFile, FALSE)) {
1166                 DisplayFatalError(_("Bad game file"), 0, 1);
1167                 return;
1168             }
1169         } else if (*appData.loadPositionFile != NULLCHAR) {
1170             int index = appData.loadPositionIndex; // [HGM] autoinc
1171             if(index<0) lastIndex = index = 1;
1172             if (!LoadPositionFromFile(appData.loadPositionFile,
1173                                       index,
1174                                       appData.loadPositionFile)) {
1175                 DisplayFatalError(_("Bad position file"), 0, 1);
1176                 return;
1177             }
1178         }
1179         TwoMachinesEvent();
1180     } else if (*appData.cmailGameName != NULLCHAR) {
1181         /* Set up cmail mode */
1182         ReloadCmailMsgEvent(TRUE);
1183     } else {
1184         /* Set up other modes */
1185         if (initialMode == AnalyzeFile) {
1186           if (*appData.loadGameFile == NULLCHAR) {
1187             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1188             return;
1189           }
1190         }
1191         if (*appData.loadGameFile != NULLCHAR) {
1192             (void) LoadGameFromFile(appData.loadGameFile,
1193                                     appData.loadGameIndex,
1194                                     appData.loadGameFile, TRUE);
1195         } else if (*appData.loadPositionFile != NULLCHAR) {
1196             (void) LoadPositionFromFile(appData.loadPositionFile,
1197                                         appData.loadPositionIndex,
1198                                         appData.loadPositionFile);
1199             /* [HGM] try to make self-starting even after FEN load */
1200             /* to allow automatic setup of fairy variants with wtm */
1201             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1202                 gameMode = BeginningOfGame;
1203                 setboardSpoiledMachineBlack = 1;
1204             }
1205             /* [HGM] loadPos: make that every new game uses the setup */
1206             /* from file as long as we do not switch variant          */
1207             if(!blackPlaysFirst) {
1208                 startedFromPositionFile = TRUE;
1209                 CopyBoard(filePosition, boards[0]);
1210             }
1211         }
1212         if (initialMode == AnalyzeMode) {
1213           if (appData.noChessProgram) {
1214             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1215             return;
1216           }
1217           if (appData.icsActive) {
1218             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1219             return;
1220           }
1221           AnalyzeModeEvent();
1222         } else if (initialMode == AnalyzeFile) {
1223           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1224           ShowThinkingEvent();
1225           AnalyzeFileEvent();
1226           AnalysisPeriodicEvent(1);
1227         } else if (initialMode == MachinePlaysWhite) {
1228           if (appData.noChessProgram) {
1229             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1230                               0, 2);
1231             return;
1232           }
1233           if (appData.icsActive) {
1234             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1235                               0, 2);
1236             return;
1237           }
1238           MachineWhiteEvent();
1239         } else if (initialMode == MachinePlaysBlack) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1242                               0, 2);
1243             return;
1244           }
1245           if (appData.icsActive) {
1246             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1247                               0, 2);
1248             return;
1249           }
1250           MachineBlackEvent();
1251         } else if (initialMode == TwoMachinesPlay) {
1252           if (appData.noChessProgram) {
1253             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1254                               0, 2);
1255             return;
1256           }
1257           if (appData.icsActive) {
1258             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1259                               0, 2);
1260             return;
1261           }
1262           TwoMachinesEvent();
1263         } else if (initialMode == EditGame) {
1264           EditGameEvent();
1265         } else if (initialMode == EditPosition) {
1266           EditPositionEvent();
1267         } else if (initialMode == Training) {
1268           if (*appData.loadGameFile == NULLCHAR) {
1269             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1270             return;
1271           }
1272           TrainingEvent();
1273         }
1274     }
1275 }
1276
1277 /*
1278  * Establish will establish a contact to a remote host.port.
1279  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1280  *  used to talk to the host.
1281  * Returns 0 if okay, error code if not.
1282  */
1283 int
1284 establish()
1285 {
1286     char buf[MSG_SIZ];
1287
1288     if (*appData.icsCommPort != NULLCHAR) {
1289         /* Talk to the host through a serial comm port */
1290         return OpenCommPort(appData.icsCommPort, &icsPR);
1291
1292     } else if (*appData.gateway != NULLCHAR) {
1293         if (*appData.remoteShell == NULLCHAR) {
1294             /* Use the rcmd protocol to run telnet program on a gateway host */
1295             snprintf(buf, sizeof(buf), "%s %s %s",
1296                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1297             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1298
1299         } else {
1300             /* Use the rsh program to run telnet program on a gateway host */
1301             if (*appData.remoteUser == NULLCHAR) {
1302                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1303                         appData.gateway, appData.telnetProgram,
1304                         appData.icsHost, appData.icsPort);
1305             } else {
1306                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1307                         appData.remoteShell, appData.gateway,
1308                         appData.remoteUser, appData.telnetProgram,
1309                         appData.icsHost, appData.icsPort);
1310             }
1311             return StartChildProcess(buf, "", &icsPR);
1312
1313         }
1314     } else if (appData.useTelnet) {
1315         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1316
1317     } else {
1318         /* TCP socket interface differs somewhat between
1319            Unix and NT; handle details in the front end.
1320            */
1321         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1322     }
1323 }
1324
1325 void
1326 show_bytes(fp, buf, count)
1327      FILE *fp;
1328      char *buf;
1329      int count;
1330 {
1331     while (count--) {
1332         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1333             fprintf(fp, "\\%03o", *buf & 0xff);
1334         } else {
1335             putc(*buf, fp);
1336         }
1337         buf++;
1338     }
1339     fflush(fp);
1340 }
1341
1342 /* Returns an errno value */
1343 int
1344 OutputMaybeTelnet(pr, message, count, outError)
1345      ProcRef pr;
1346      char *message;
1347      int count;
1348      int *outError;
1349 {
1350     char buf[8192], *p, *q, *buflim;
1351     int left, newcount, outcount;
1352
1353     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1354         *appData.gateway != NULLCHAR) {
1355         if (appData.debugMode) {
1356             fprintf(debugFP, ">ICS: ");
1357             show_bytes(debugFP, message, count);
1358             fprintf(debugFP, "\n");
1359         }
1360         return OutputToProcess(pr, message, count, outError);
1361     }
1362
1363     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1364     p = message;
1365     q = buf;
1366     left = count;
1367     newcount = 0;
1368     while (left) {
1369         if (q >= buflim) {
1370             if (appData.debugMode) {
1371                 fprintf(debugFP, ">ICS: ");
1372                 show_bytes(debugFP, buf, newcount);
1373                 fprintf(debugFP, "\n");
1374             }
1375             outcount = OutputToProcess(pr, buf, newcount, outError);
1376             if (outcount < newcount) return -1; /* to be sure */
1377             q = buf;
1378             newcount = 0;
1379         }
1380         if (*p == '\n') {
1381             *q++ = '\r';
1382             newcount++;
1383         } else if (((unsigned char) *p) == TN_IAC) {
1384             *q++ = (char) TN_IAC;
1385             newcount ++;
1386         }
1387         *q++ = *p++;
1388         newcount++;
1389         left--;
1390     }
1391     if (appData.debugMode) {
1392         fprintf(debugFP, ">ICS: ");
1393         show_bytes(debugFP, buf, newcount);
1394         fprintf(debugFP, "\n");
1395     }
1396     outcount = OutputToProcess(pr, buf, newcount, outError);
1397     if (outcount < newcount) return -1; /* to be sure */
1398     return count;
1399 }
1400
1401 void
1402 read_from_player(isr, closure, message, count, error)
1403      InputSourceRef isr;
1404      VOIDSTAR closure;
1405      char *message;
1406      int count;
1407      int error;
1408 {
1409     int outError, outCount;
1410     static int gotEof = 0;
1411
1412     /* Pass data read from player on to ICS */
1413     if (count > 0) {
1414         gotEof = 0;
1415         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1416         if (outCount < count) {
1417             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1418         }
1419     } else if (count < 0) {
1420         RemoveInputSource(isr);
1421         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1422     } else if (gotEof++ > 0) {
1423         RemoveInputSource(isr);
1424         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1425     }
1426 }
1427
1428 void
1429 KeepAlive()
1430 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1431     SendToICS("date\n");
1432     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1433 }
1434
1435 /* added routine for printf style output to ics */
1436 void ics_printf(char *format, ...)
1437 {
1438     char buffer[MSG_SIZ];
1439     va_list args;
1440
1441     va_start(args, format);
1442     vsnprintf(buffer, sizeof(buffer), format, args);
1443     buffer[sizeof(buffer)-1] = '\0';
1444     SendToICS(buffer);
1445     va_end(args);
1446 }
1447
1448 void
1449 SendToICS(s)
1450      char *s;
1451 {
1452     int count, outCount, outError;
1453
1454     if (icsPR == NULL) return;
1455
1456     count = strlen(s);
1457     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1458     if (outCount < count) {
1459         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1460     }
1461 }
1462
1463 /* This is used for sending logon scripts to the ICS. Sending
1464    without a delay causes problems when using timestamp on ICC
1465    (at least on my machine). */
1466 void
1467 SendToICSDelayed(s,msdelay)
1468      char *s;
1469      long msdelay;
1470 {
1471     int count, outCount, outError;
1472
1473     if (icsPR == NULL) return;
1474
1475     count = strlen(s);
1476     if (appData.debugMode) {
1477         fprintf(debugFP, ">ICS: ");
1478         show_bytes(debugFP, s, count);
1479         fprintf(debugFP, "\n");
1480     }
1481     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1482                                       msdelay);
1483     if (outCount < count) {
1484         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1485     }
1486 }
1487
1488
1489 /* Remove all highlighting escape sequences in s
1490    Also deletes any suffix starting with '('
1491    */
1492 char *
1493 StripHighlightAndTitle(s)
1494      char *s;
1495 {
1496     static char retbuf[MSG_SIZ];
1497     char *p = retbuf;
1498
1499     while (*s != NULLCHAR) {
1500         while (*s == '\033') {
1501             while (*s != NULLCHAR && !isalpha(*s)) s++;
1502             if (*s != NULLCHAR) s++;
1503         }
1504         while (*s != NULLCHAR && *s != '\033') {
1505             if (*s == '(' || *s == '[') {
1506                 *p = NULLCHAR;
1507                 return retbuf;
1508             }
1509             *p++ = *s++;
1510         }
1511     }
1512     *p = NULLCHAR;
1513     return retbuf;
1514 }
1515
1516 /* Remove all highlighting escape sequences in s */
1517 char *
1518 StripHighlight(s)
1519      char *s;
1520 {
1521     static char retbuf[MSG_SIZ];
1522     char *p = retbuf;
1523
1524     while (*s != NULLCHAR) {
1525         while (*s == '\033') {
1526             while (*s != NULLCHAR && !isalpha(*s)) s++;
1527             if (*s != NULLCHAR) s++;
1528         }
1529         while (*s != NULLCHAR && *s != '\033') {
1530             *p++ = *s++;
1531         }
1532     }
1533     *p = NULLCHAR;
1534     return retbuf;
1535 }
1536
1537 char *variantNames[] = VARIANT_NAMES;
1538 char *
1539 VariantName(v)
1540      VariantClass v;
1541 {
1542     return variantNames[v];
1543 }
1544
1545
1546 /* Identify a variant from the strings the chess servers use or the
1547    PGN Variant tag names we use. */
1548 VariantClass
1549 StringToVariant(e)
1550      char *e;
1551 {
1552     char *p;
1553     int wnum = -1;
1554     VariantClass v = VariantNormal;
1555     int i, found = FALSE;
1556     char buf[MSG_SIZ];
1557
1558     if (!e) return v;
1559
1560     /* [HGM] skip over optional board-size prefixes */
1561     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1562         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1563         while( *e++ != '_');
1564     }
1565
1566     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1567         v = VariantNormal;
1568         found = TRUE;
1569     } else
1570     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1571       if (StrCaseStr(e, variantNames[i])) {
1572         v = (VariantClass) i;
1573         found = TRUE;
1574         break;
1575       }
1576     }
1577
1578     if (!found) {
1579       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1580           || StrCaseStr(e, "wild/fr")
1581           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1582         v = VariantFischeRandom;
1583       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1584                  (i = 1, p = StrCaseStr(e, "w"))) {
1585         p += i;
1586         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1587         if (isdigit(*p)) {
1588           wnum = atoi(p);
1589         } else {
1590           wnum = -1;
1591         }
1592         switch (wnum) {
1593         case 0: /* FICS only, actually */
1594         case 1:
1595           /* Castling legal even if K starts on d-file */
1596           v = VariantWildCastle;
1597           break;
1598         case 2:
1599         case 3:
1600         case 4:
1601           /* Castling illegal even if K & R happen to start in
1602              normal positions. */
1603           v = VariantNoCastle;
1604           break;
1605         case 5:
1606         case 7:
1607         case 8:
1608         case 10:
1609         case 11:
1610         case 12:
1611         case 13:
1612         case 14:
1613         case 15:
1614         case 18:
1615         case 19:
1616           /* Castling legal iff K & R start in normal positions */
1617           v = VariantNormal;
1618           break;
1619         case 6:
1620         case 20:
1621         case 21:
1622           /* Special wilds for position setup; unclear what to do here */
1623           v = VariantLoadable;
1624           break;
1625         case 9:
1626           /* Bizarre ICC game */
1627           v = VariantTwoKings;
1628           break;
1629         case 16:
1630           v = VariantKriegspiel;
1631           break;
1632         case 17:
1633           v = VariantLosers;
1634           break;
1635         case 22:
1636           v = VariantFischeRandom;
1637           break;
1638         case 23:
1639           v = VariantCrazyhouse;
1640           break;
1641         case 24:
1642           v = VariantBughouse;
1643           break;
1644         case 25:
1645           v = Variant3Check;
1646           break;
1647         case 26:
1648           /* Not quite the same as FICS suicide! */
1649           v = VariantGiveaway;
1650           break;
1651         case 27:
1652           v = VariantAtomic;
1653           break;
1654         case 28:
1655           v = VariantShatranj;
1656           break;
1657
1658         /* Temporary names for future ICC types.  The name *will* change in
1659            the next xboard/WinBoard release after ICC defines it. */
1660         case 29:
1661           v = Variant29;
1662           break;
1663         case 30:
1664           v = Variant30;
1665           break;
1666         case 31:
1667           v = Variant31;
1668           break;
1669         case 32:
1670           v = Variant32;
1671           break;
1672         case 33:
1673           v = Variant33;
1674           break;
1675         case 34:
1676           v = Variant34;
1677           break;
1678         case 35:
1679           v = Variant35;
1680           break;
1681         case 36:
1682           v = Variant36;
1683           break;
1684         case 37:
1685           v = VariantShogi;
1686           break;
1687         case 38:
1688           v = VariantXiangqi;
1689           break;
1690         case 39:
1691           v = VariantCourier;
1692           break;
1693         case 40:
1694           v = VariantGothic;
1695           break;
1696         case 41:
1697           v = VariantCapablanca;
1698           break;
1699         case 42:
1700           v = VariantKnightmate;
1701           break;
1702         case 43:
1703           v = VariantFairy;
1704           break;
1705         case 44:
1706           v = VariantCylinder;
1707           break;
1708         case 45:
1709           v = VariantFalcon;
1710           break;
1711         case 46:
1712           v = VariantCapaRandom;
1713           break;
1714         case 47:
1715           v = VariantBerolina;
1716           break;
1717         case 48:
1718           v = VariantJanus;
1719           break;
1720         case 49:
1721           v = VariantSuper;
1722           break;
1723         case 50:
1724           v = VariantGreat;
1725           break;
1726         case -1:
1727           /* Found "wild" or "w" in the string but no number;
1728              must assume it's normal chess. */
1729           v = VariantNormal;
1730           break;
1731         default:
1732           sprintf(buf, _("Unknown wild type %d"), wnum);
1733           DisplayError(buf, 0);
1734           v = VariantUnknown;
1735           break;
1736         }
1737       }
1738     }
1739     if (appData.debugMode) {
1740       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1741               e, wnum, VariantName(v));
1742     }
1743     return v;
1744 }
1745
1746 static int leftover_start = 0, leftover_len = 0;
1747 char star_match[STAR_MATCH_N][MSG_SIZ];
1748
1749 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1750    advance *index beyond it, and set leftover_start to the new value of
1751    *index; else return FALSE.  If pattern contains the character '*', it
1752    matches any sequence of characters not containing '\r', '\n', or the
1753    character following the '*' (if any), and the matched sequence(s) are
1754    copied into star_match.
1755    */
1756 int
1757 looking_at(buf, index, pattern)
1758      char *buf;
1759      int *index;
1760      char *pattern;
1761 {
1762     char *bufp = &buf[*index], *patternp = pattern;
1763     int star_count = 0;
1764     char *matchp = star_match[0];
1765
1766     for (;;) {
1767         if (*patternp == NULLCHAR) {
1768             *index = leftover_start = bufp - buf;
1769             *matchp = NULLCHAR;
1770             return TRUE;
1771         }
1772         if (*bufp == NULLCHAR) return FALSE;
1773         if (*patternp == '*') {
1774             if (*bufp == *(patternp + 1)) {
1775                 *matchp = NULLCHAR;
1776                 matchp = star_match[++star_count];
1777                 patternp += 2;
1778                 bufp++;
1779                 continue;
1780             } else if (*bufp == '\n' || *bufp == '\r') {
1781                 patternp++;
1782                 if (*patternp == NULLCHAR)
1783                   continue;
1784                 else
1785                   return FALSE;
1786             } else {
1787                 *matchp++ = *bufp++;
1788                 continue;
1789             }
1790         }
1791         if (*patternp != *bufp) return FALSE;
1792         patternp++;
1793         bufp++;
1794     }
1795 }
1796
1797 void
1798 SendToPlayer(data, length)
1799      char *data;
1800      int length;
1801 {
1802     int error, outCount;
1803     outCount = OutputToProcess(NoProc, data, length, &error);
1804     if (outCount < length) {
1805         DisplayFatalError(_("Error writing to display"), error, 1);
1806     }
1807 }
1808
1809 void
1810 PackHolding(packed, holding)
1811      char packed[];
1812      char *holding;
1813 {
1814     char *p = holding;
1815     char *q = packed;
1816     int runlength = 0;
1817     int curr = 9999;
1818     do {
1819         if (*p == curr) {
1820             runlength++;
1821         } else {
1822             switch (runlength) {
1823               case 0:
1824                 break;
1825               case 1:
1826                 *q++ = curr;
1827                 break;
1828               case 2:
1829                 *q++ = curr;
1830                 *q++ = curr;
1831                 break;
1832               default:
1833                 sprintf(q, "%d", runlength);
1834                 while (*q) q++;
1835                 *q++ = curr;
1836                 break;
1837             }
1838             runlength = 1;
1839             curr = *p;
1840         }
1841     } while (*p++);
1842     *q = NULLCHAR;
1843 }
1844
1845 /* Telnet protocol requests from the front end */
1846 void
1847 TelnetRequest(ddww, option)
1848      unsigned char ddww, option;
1849 {
1850     unsigned char msg[3];
1851     int outCount, outError;
1852
1853     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1854
1855     if (appData.debugMode) {
1856         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1857         switch (ddww) {
1858           case TN_DO:
1859             ddwwStr = "DO";
1860             break;
1861           case TN_DONT:
1862             ddwwStr = "DONT";
1863             break;
1864           case TN_WILL:
1865             ddwwStr = "WILL";
1866             break;
1867           case TN_WONT:
1868             ddwwStr = "WONT";
1869             break;
1870           default:
1871             ddwwStr = buf1;
1872             sprintf(buf1, "%d", ddww);
1873             break;
1874         }
1875         switch (option) {
1876           case TN_ECHO:
1877             optionStr = "ECHO";
1878             break;
1879           default:
1880             optionStr = buf2;
1881             sprintf(buf2, "%d", option);
1882             break;
1883         }
1884         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1885     }
1886     msg[0] = TN_IAC;
1887     msg[1] = ddww;
1888     msg[2] = option;
1889     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1890     if (outCount < 3) {
1891         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1892     }
1893 }
1894
1895 void
1896 DoEcho()
1897 {
1898     if (!appData.icsActive) return;
1899     TelnetRequest(TN_DO, TN_ECHO);
1900 }
1901
1902 void
1903 DontEcho()
1904 {
1905     if (!appData.icsActive) return;
1906     TelnetRequest(TN_DONT, TN_ECHO);
1907 }
1908
1909 void
1910 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1911 {
1912     /* put the holdings sent to us by the server on the board holdings area */
1913     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1914     char p;
1915     ChessSquare piece;
1916
1917     if(gameInfo.holdingsWidth < 2)  return;
1918     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1919         return; // prevent overwriting by pre-board holdings
1920
1921     if( (int)lowestPiece >= BlackPawn ) {
1922         holdingsColumn = 0;
1923         countsColumn = 1;
1924         holdingsStartRow = BOARD_HEIGHT-1;
1925         direction = -1;
1926     } else {
1927         holdingsColumn = BOARD_WIDTH-1;
1928         countsColumn = BOARD_WIDTH-2;
1929         holdingsStartRow = 0;
1930         direction = 1;
1931     }
1932
1933     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934         board[i][holdingsColumn] = EmptySquare;
1935         board[i][countsColumn]   = (ChessSquare) 0;
1936     }
1937     while( (p=*holdings++) != NULLCHAR ) {
1938         piece = CharToPiece( ToUpper(p) );
1939         if(piece == EmptySquare) continue;
1940         /*j = (int) piece - (int) WhitePawn;*/
1941         j = PieceToNumber(piece);
1942         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943         if(j < 0) continue;               /* should not happen */
1944         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946         board[holdingsStartRow+j*direction][countsColumn]++;
1947     }
1948 }
1949
1950
1951 void
1952 VariantSwitch(Board board, VariantClass newVariant)
1953 {
1954    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1955    Board oldBoard;
1956
1957    startedFromPositionFile = FALSE;
1958    if(gameInfo.variant == newVariant) return;
1959
1960    /* [HGM] This routine is called each time an assignment is made to
1961     * gameInfo.variant during a game, to make sure the board sizes
1962     * are set to match the new variant. If that means adding or deleting
1963     * holdings, we shift the playing board accordingly
1964     * This kludge is needed because in ICS observe mode, we get boards
1965     * of an ongoing game without knowing the variant, and learn about the
1966     * latter only later. This can be because of the move list we requested,
1967     * in which case the game history is refilled from the beginning anyway,
1968     * but also when receiving holdings of a crazyhouse game. In the latter
1969     * case we want to add those holdings to the already received position.
1970     */
1971    
1972    if (appData.debugMode) {
1973      fprintf(debugFP, "Switch board from %s to %s\n",
1974              VariantName(gameInfo.variant), VariantName(newVariant));
1975      setbuf(debugFP, NULL);
1976    }
1977    shuffleOpenings = 0;       /* [HGM] shuffle */
1978    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1979    switch(newVariant) 
1980      {
1981      case VariantShogi:
1982        newWidth = 9;  newHeight = 9;
1983        gameInfo.holdingsSize = 7;
1984      case VariantBughouse:
1985      case VariantCrazyhouse:
1986        newHoldingsWidth = 2; break;
1987      case VariantGreat:
1988        newWidth = 10;
1989      case VariantSuper:
1990        newHoldingsWidth = 2;
1991        gameInfo.holdingsSize = 8;
1992        break;
1993      case VariantGothic:
1994      case VariantCapablanca:
1995      case VariantCapaRandom:
1996        newWidth = 10;
1997      default:
1998        newHoldingsWidth = gameInfo.holdingsSize = 0;
1999      };
2000    
2001    if(newWidth  != gameInfo.boardWidth  ||
2002       newHeight != gameInfo.boardHeight ||
2003       newHoldingsWidth != gameInfo.holdingsWidth ) {
2004      
2005      /* shift position to new playing area, if needed */
2006      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2007        for(i=0; i<BOARD_HEIGHT; i++) 
2008          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2009            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2010              board[i][j];
2011        for(i=0; i<newHeight; i++) {
2012          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2013          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2014        }
2015      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2016        for(i=0; i<BOARD_HEIGHT; i++)
2017          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2018            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2019              board[i][j];
2020      }
2021      gameInfo.boardWidth  = newWidth;
2022      gameInfo.boardHeight = newHeight;
2023      gameInfo.holdingsWidth = newHoldingsWidth;
2024      gameInfo.variant = newVariant;
2025      InitDrawingSizes(-2, 0);
2026    } else gameInfo.variant = newVariant;
2027    CopyBoard(oldBoard, board);   // remember correctly formatted board
2028      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2029    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2030 }
2031
2032 static int loggedOn = FALSE;
2033
2034 /*-- Game start info cache: --*/
2035 int gs_gamenum;
2036 char gs_kind[MSG_SIZ];
2037 static char player1Name[128] = "";
2038 static char player2Name[128] = "";
2039 static char cont_seq[] = "\n\\   ";
2040 static int player1Rating = -1;
2041 static int player2Rating = -1;
2042 /*----------------------------*/
2043
2044 ColorClass curColor = ColorNormal;
2045 int suppressKibitz = 0;
2046
2047 void
2048 read_from_ics(isr, closure, data, count, error)
2049      InputSourceRef isr;
2050      VOIDSTAR closure;
2051      char *data;
2052      int count;
2053      int error;
2054 {
2055 #define BUF_SIZE 8192
2056 #define STARTED_NONE 0
2057 #define STARTED_MOVES 1
2058 #define STARTED_BOARD 2
2059 #define STARTED_OBSERVE 3
2060 #define STARTED_HOLDINGS 4
2061 #define STARTED_CHATTER 5
2062 #define STARTED_COMMENT 6
2063 #define STARTED_MOVES_NOHIDE 7
2064
2065     static int started = STARTED_NONE;
2066     static char parse[20000];
2067     static int parse_pos = 0;
2068     static char buf[BUF_SIZE + 1];
2069     static int firstTime = TRUE, intfSet = FALSE;
2070     static ColorClass prevColor = ColorNormal;
2071     static int savingComment = FALSE;
2072     static int cmatch = 0; // continuation sequence match
2073     char *bp;
2074     char str[500];
2075     int i, oldi;
2076     int buf_len;
2077     int next_out;
2078     int tkind;
2079     int backup;    /* [DM] For zippy color lines */
2080     char *p;
2081     char talker[MSG_SIZ]; // [HGM] chat
2082     int channel;
2083
2084     if (appData.debugMode) {
2085       if (!error) {
2086         fprintf(debugFP, "<ICS: ");
2087         show_bytes(debugFP, data, count);
2088         fprintf(debugFP, "\n");
2089       }
2090     }
2091
2092     if (appData.debugMode) { int f = forwardMostMove;
2093         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2094                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2095                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2096     }
2097     if (count > 0) {
2098         /* If last read ended with a partial line that we couldn't parse,
2099            prepend it to the new read and try again. */
2100         if (leftover_len > 0) {
2101             for (i=0; i<leftover_len; i++)
2102               buf[i] = buf[leftover_start + i];
2103         }
2104
2105     /* copy new characters into the buffer */
2106     bp = buf + leftover_len;
2107     buf_len=leftover_len;
2108     for (i=0; i<count; i++)
2109     {
2110         // ignore these
2111         if (data[i] == '\r')
2112             continue;
2113
2114         // join lines split by ICS?
2115         if (!appData.noJoin)
2116         {
2117             /*
2118                 Joining just consists of finding matches against the
2119                 continuation sequence, and discarding that sequence
2120                 if found instead of copying it.  So, until a match
2121                 fails, there's nothing to do since it might be the
2122                 complete sequence, and thus, something we don't want
2123                 copied.
2124             */
2125             if (data[i] == cont_seq[cmatch])
2126             {
2127                 cmatch++;
2128                 if (cmatch == strlen(cont_seq))
2129                 {
2130                     cmatch = 0; // complete match.  just reset the counter
2131
2132                     /*
2133                         it's possible for the ICS to not include the space
2134                         at the end of the last word, making our [correct]
2135                         join operation fuse two separate words.  the server
2136                         does this when the space occurs at the width setting.
2137                     */
2138                     if (!buf_len || buf[buf_len-1] != ' ')
2139                     {
2140                         *bp++ = ' ';
2141                         buf_len++;
2142                     }
2143                 }
2144                 continue;
2145             }
2146             else if (cmatch)
2147             {
2148                 /*
2149                     match failed, so we have to copy what matched before
2150                     falling through and copying this character.  In reality,
2151                     this will only ever be just the newline character, but
2152                     it doesn't hurt to be precise.
2153                 */
2154                 strncpy(bp, cont_seq, cmatch);
2155                 bp += cmatch;
2156                 buf_len += cmatch;
2157                 cmatch = 0;
2158             }
2159         }
2160
2161         // copy this char
2162         *bp++ = data[i];
2163         buf_len++;
2164     }
2165
2166         buf[buf_len] = NULLCHAR;
2167         next_out = leftover_len;
2168         leftover_start = 0;
2169
2170         i = 0;
2171         while (i < buf_len) {
2172             /* Deal with part of the TELNET option negotiation
2173                protocol.  We refuse to do anything beyond the
2174                defaults, except that we allow the WILL ECHO option,
2175                which ICS uses to turn off password echoing when we are
2176                directly connected to it.  We reject this option
2177                if localLineEditing mode is on (always on in xboard)
2178                and we are talking to port 23, which might be a real
2179                telnet server that will try to keep WILL ECHO on permanently.
2180              */
2181             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2182                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2183                 unsigned char option;
2184                 oldi = i;
2185                 switch ((unsigned char) buf[++i]) {
2186                   case TN_WILL:
2187                     if (appData.debugMode)
2188                       fprintf(debugFP, "\n<WILL ");
2189                     switch (option = (unsigned char) buf[++i]) {
2190                       case TN_ECHO:
2191                         if (appData.debugMode)
2192                           fprintf(debugFP, "ECHO ");
2193                         /* Reply only if this is a change, according
2194                            to the protocol rules. */
2195                         if (remoteEchoOption) break;
2196                         if (appData.localLineEditing &&
2197                             atoi(appData.icsPort) == TN_PORT) {
2198                             TelnetRequest(TN_DONT, TN_ECHO);
2199                         } else {
2200                             EchoOff();
2201                             TelnetRequest(TN_DO, TN_ECHO);
2202                             remoteEchoOption = TRUE;
2203                         }
2204                         break;
2205                       default:
2206                         if (appData.debugMode)
2207                           fprintf(debugFP, "%d ", option);
2208                         /* Whatever this is, we don't want it. */
2209                         TelnetRequest(TN_DONT, option);
2210                         break;
2211                     }
2212                     break;
2213                   case TN_WONT:
2214                     if (appData.debugMode)
2215                       fprintf(debugFP, "\n<WONT ");
2216                     switch (option = (unsigned char) buf[++i]) {
2217                       case TN_ECHO:
2218                         if (appData.debugMode)
2219                           fprintf(debugFP, "ECHO ");
2220                         /* Reply only if this is a change, according
2221                            to the protocol rules. */
2222                         if (!remoteEchoOption) break;
2223                         EchoOn();
2224                         TelnetRequest(TN_DONT, TN_ECHO);
2225                         remoteEchoOption = FALSE;
2226                         break;
2227                       default:
2228                         if (appData.debugMode)
2229                           fprintf(debugFP, "%d ", (unsigned char) option);
2230                         /* Whatever this is, it must already be turned
2231                            off, because we never agree to turn on
2232                            anything non-default, so according to the
2233                            protocol rules, we don't reply. */
2234                         break;
2235                     }
2236                     break;
2237                   case TN_DO:
2238                     if (appData.debugMode)
2239                       fprintf(debugFP, "\n<DO ");
2240                     switch (option = (unsigned char) buf[++i]) {
2241                       default:
2242                         /* Whatever this is, we refuse to do it. */
2243                         if (appData.debugMode)
2244                           fprintf(debugFP, "%d ", option);
2245                         TelnetRequest(TN_WONT, option);
2246                         break;
2247                     }
2248                     break;
2249                   case TN_DONT:
2250                     if (appData.debugMode)
2251                       fprintf(debugFP, "\n<DONT ");
2252                     switch (option = (unsigned char) buf[++i]) {
2253                       default:
2254                         if (appData.debugMode)
2255                           fprintf(debugFP, "%d ", option);
2256                         /* Whatever this is, we are already not doing
2257                            it, because we never agree to do anything
2258                            non-default, so according to the protocol
2259                            rules, we don't reply. */
2260                         break;
2261                     }
2262                     break;
2263                   case TN_IAC:
2264                     if (appData.debugMode)
2265                       fprintf(debugFP, "\n<IAC ");
2266                     /* Doubled IAC; pass it through */
2267                     i--;
2268                     break;
2269                   default:
2270                     if (appData.debugMode)
2271                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2272                     /* Drop all other telnet commands on the floor */
2273                     break;
2274                 }
2275                 if (oldi > next_out)
2276                   SendToPlayer(&buf[next_out], oldi - next_out);
2277                 if (++i > next_out)
2278                   next_out = i;
2279                 continue;
2280             }
2281
2282             /* OK, this at least will *usually* work */
2283             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2284                 loggedOn = TRUE;
2285             }
2286
2287             if (loggedOn && !intfSet) {
2288                 if (ics_type == ICS_ICC) {
2289                   sprintf(str,
2290                           "/set-quietly interface %s\n/set-quietly style 12\n",
2291                           programVersion);
2292                 } else if (ics_type == ICS_CHESSNET) {
2293                   sprintf(str, "/style 12\n");
2294                 } else {
2295                   strcpy(str, "alias $ @\n$set interface ");
2296                   strcat(str, programVersion);
2297                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2298 #ifdef WIN32
2299                   strcat(str, "$iset nohighlight 1\n");
2300 #endif
2301                   strcat(str, "$iset lock 1\n$style 12\n");
2302                 }
2303                 SendToICS(str);
2304                 NotifyFrontendLogin();
2305                 intfSet = TRUE;
2306             }
2307
2308             if (started == STARTED_COMMENT) {
2309                 /* Accumulate characters in comment */
2310                 parse[parse_pos++] = buf[i];
2311                 if (buf[i] == '\n') {
2312                     parse[parse_pos] = NULLCHAR;
2313                     if(chattingPartner>=0) {
2314                         char mess[MSG_SIZ];
2315                         sprintf(mess, "%s%s", talker, parse);
2316                         OutputChatMessage(chattingPartner, mess);
2317                         chattingPartner = -1;
2318                     } else
2319                     if(!suppressKibitz) // [HGM] kibitz
2320                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2321                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2322                         int nrDigit = 0, nrAlph = 0, i;
2323                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2324                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2325                         parse[parse_pos] = NULLCHAR;
2326                         // try to be smart: if it does not look like search info, it should go to
2327                         // ICS interaction window after all, not to engine-output window.
2328                         for(i=0; i<parse_pos; i++) { // count letters and digits
2329                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2330                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2331                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2332                         }
2333                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2334                             int depth=0; float score;
2335                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2336                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2337                                 pvInfoList[forwardMostMove-1].depth = depth;
2338                                 pvInfoList[forwardMostMove-1].score = 100*score;
2339                             }
2340                             OutputKibitz(suppressKibitz, parse);
2341                         } else {
2342                             char tmp[MSG_SIZ];
2343                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2344                             SendToPlayer(tmp, strlen(tmp));
2345                         }
2346                     }
2347                     started = STARTED_NONE;
2348                 } else {
2349                     /* Don't match patterns against characters in chatter */
2350                     i++;
2351                     continue;
2352                 }
2353             }
2354             if (started == STARTED_CHATTER) {
2355                 if (buf[i] != '\n') {
2356                     /* Don't match patterns against characters in chatter */
2357                     i++;
2358                     continue;
2359                 }
2360                 started = STARTED_NONE;
2361             }
2362
2363             /* Kludge to deal with rcmd protocol */
2364             if (firstTime && looking_at(buf, &i, "\001*")) {
2365                 DisplayFatalError(&buf[1], 0, 1);
2366                 continue;
2367             } else {
2368                 firstTime = FALSE;
2369             }
2370
2371             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2372                 ics_type = ICS_ICC;
2373                 ics_prefix = "/";
2374                 if (appData.debugMode)
2375                   fprintf(debugFP, "ics_type %d\n", ics_type);
2376                 continue;
2377             }
2378             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2379                 ics_type = ICS_FICS;
2380                 ics_prefix = "$";
2381                 if (appData.debugMode)
2382                   fprintf(debugFP, "ics_type %d\n", ics_type);
2383                 continue;
2384             }
2385             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2386                 ics_type = ICS_CHESSNET;
2387                 ics_prefix = "/";
2388                 if (appData.debugMode)
2389                   fprintf(debugFP, "ics_type %d\n", ics_type);
2390                 continue;
2391             }
2392
2393             if (!loggedOn &&
2394                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2395                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2396                  looking_at(buf, &i, "will be \"*\""))) {
2397               strcpy(ics_handle, star_match[0]);
2398               continue;
2399             }
2400
2401             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2402               char buf[MSG_SIZ];
2403               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2404               DisplayIcsInteractionTitle(buf);
2405               have_set_title = TRUE;
2406             }
2407
2408             /* skip finger notes */
2409             if (started == STARTED_NONE &&
2410                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2411                  (buf[i] == '1' && buf[i+1] == '0')) &&
2412                 buf[i+2] == ':' && buf[i+3] == ' ') {
2413               started = STARTED_CHATTER;
2414               i += 3;
2415               continue;
2416             }
2417
2418             /* skip formula vars */
2419             if (started == STARTED_NONE &&
2420                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2421               started = STARTED_CHATTER;
2422               i += 3;
2423               continue;
2424             }
2425
2426             oldi = i;
2427             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2428             if (appData.autoKibitz && started == STARTED_NONE &&
2429                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2430                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2431                 if(looking_at(buf, &i, "* kibitzes: ") &&
2432                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2433                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2434                         suppressKibitz = TRUE;
2435                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2436                                 && (gameMode == IcsPlayingWhite)) ||
2437                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2438                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2439                             started = STARTED_CHATTER; // own kibitz we simply discard
2440                         else {
2441                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2442                             parse_pos = 0; parse[0] = NULLCHAR;
2443                             savingComment = TRUE;
2444                             suppressKibitz = gameMode != IcsObserving ? 2 :
2445                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2446                         }
2447                         continue;
2448                 } else
2449                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2450                     started = STARTED_CHATTER;
2451                     suppressKibitz = TRUE;
2452                 }
2453             } // [HGM] kibitz: end of patch
2454
2455 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2456
2457             // [HGM] chat: intercept tells by users for which we have an open chat window
2458             channel = -1;
2459             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2460                                            looking_at(buf, &i, "* whispers:") ||
2461                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2462                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2463                 int p;
2464                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2465                 chattingPartner = -1;
2466
2467                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2468                 for(p=0; p<MAX_CHAT; p++) {
2469                     if(channel == atoi(chatPartner[p])) {
2470                     talker[0] = '['; strcat(talker, "]");
2471                     chattingPartner = p; break;
2472                     }
2473                 } else
2474                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2475                 for(p=0; p<MAX_CHAT; p++) {
2476                     if(!strcmp("WHISPER", chatPartner[p])) {
2477                         talker[0] = '['; strcat(talker, "]");
2478                         chattingPartner = p; break;
2479                     }
2480                 }
2481                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2482                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2483                     talker[0] = 0;
2484                     chattingPartner = p; break;
2485                 }
2486                 if(chattingPartner<0) i = oldi; else {
2487                     started = STARTED_COMMENT;
2488                     parse_pos = 0; parse[0] = NULLCHAR;
2489                     savingComment = TRUE;
2490                     suppressKibitz = TRUE;
2491                 }
2492             } // [HGM] chat: end of patch
2493
2494             if (appData.zippyTalk || appData.zippyPlay) {
2495                 /* [DM] Backup address for color zippy lines */
2496                 backup = i;
2497 #if ZIPPY
2498        #ifdef WIN32
2499                if (loggedOn == TRUE)
2500                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2501                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2502        #else
2503                 if (ZippyControl(buf, &i) ||
2504                     ZippyConverse(buf, &i) ||
2505                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2506                       loggedOn = TRUE;
2507                       if (!appData.colorize) continue;
2508                 }
2509        #endif
2510 #endif
2511             } // [DM] 'else { ' deleted
2512                 if (
2513                     /* Regular tells and says */
2514                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2515                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2516                     looking_at(buf, &i, "* says: ") ||
2517                     /* Don't color "message" or "messages" output */
2518                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2519                     looking_at(buf, &i, "*. * at *:*: ") ||
2520                     looking_at(buf, &i, "--* (*:*): ") ||
2521                     /* Message notifications (same color as tells) */
2522                     looking_at(buf, &i, "* has left a message ") ||
2523                     looking_at(buf, &i, "* just sent you a message:\n") ||
2524                     /* Whispers and kibitzes */
2525                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2526                     looking_at(buf, &i, "* kibitzes: ") ||
2527                     /* Channel tells */
2528                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2529
2530                   if (tkind == 1 && strchr(star_match[0], ':')) {
2531                       /* Avoid "tells you:" spoofs in channels */
2532                      tkind = 3;
2533                   }
2534                   if (star_match[0][0] == NULLCHAR ||
2535                       strchr(star_match[0], ' ') ||
2536                       (tkind == 3 && strchr(star_match[1], ' '))) {
2537                     /* Reject bogus matches */
2538                     i = oldi;
2539                   } else {
2540                     if (appData.colorize) {
2541                       if (oldi > next_out) {
2542                         SendToPlayer(&buf[next_out], oldi - next_out);
2543                         next_out = oldi;
2544                       }
2545                       switch (tkind) {
2546                       case 1:
2547                         Colorize(ColorTell, FALSE);
2548                         curColor = ColorTell;
2549                         break;
2550                       case 2:
2551                         Colorize(ColorKibitz, FALSE);
2552                         curColor = ColorKibitz;
2553                         break;
2554                       case 3:
2555                         p = strrchr(star_match[1], '(');
2556                         if (p == NULL) {
2557                           p = star_match[1];
2558                         } else {
2559                           p++;
2560                         }
2561                         if (atoi(p) == 1) {
2562                           Colorize(ColorChannel1, FALSE);
2563                           curColor = ColorChannel1;
2564                         } else {
2565                           Colorize(ColorChannel, FALSE);
2566                           curColor = ColorChannel;
2567                         }
2568                         break;
2569                       case 5:
2570                         curColor = ColorNormal;
2571                         break;
2572                       }
2573                     }
2574                     if (started == STARTED_NONE && appData.autoComment &&
2575                         (gameMode == IcsObserving ||
2576                          gameMode == IcsPlayingWhite ||
2577                          gameMode == IcsPlayingBlack)) {
2578                       parse_pos = i - oldi;
2579                       memcpy(parse, &buf[oldi], parse_pos);
2580                       parse[parse_pos] = NULLCHAR;
2581                       started = STARTED_COMMENT;
2582                       savingComment = TRUE;
2583                     } else {
2584                       started = STARTED_CHATTER;
2585                       savingComment = FALSE;
2586                     }
2587                     loggedOn = TRUE;
2588                     continue;
2589                   }
2590                 }
2591
2592                 if (looking_at(buf, &i, "* s-shouts: ") ||
2593                     looking_at(buf, &i, "* c-shouts: ")) {
2594                     if (appData.colorize) {
2595                         if (oldi > next_out) {
2596                             SendToPlayer(&buf[next_out], oldi - next_out);
2597                             next_out = oldi;
2598                         }
2599                         Colorize(ColorSShout, FALSE);
2600                         curColor = ColorSShout;
2601                     }
2602                     loggedOn = TRUE;
2603                     started = STARTED_CHATTER;
2604                     continue;
2605                 }
2606
2607                 if (looking_at(buf, &i, "--->")) {
2608                     loggedOn = TRUE;
2609                     continue;
2610                 }
2611
2612                 if (looking_at(buf, &i, "* shouts: ") ||
2613                     looking_at(buf, &i, "--> ")) {
2614                     if (appData.colorize) {
2615                         if (oldi > next_out) {
2616                             SendToPlayer(&buf[next_out], oldi - next_out);
2617                             next_out = oldi;
2618                         }
2619                         Colorize(ColorShout, FALSE);
2620                         curColor = ColorShout;
2621                     }
2622                     loggedOn = TRUE;
2623                     started = STARTED_CHATTER;
2624                     continue;
2625                 }
2626
2627                 if (looking_at( buf, &i, "Challenge:")) {
2628                     if (appData.colorize) {
2629                         if (oldi > next_out) {
2630                             SendToPlayer(&buf[next_out], oldi - next_out);
2631                             next_out = oldi;
2632                         }
2633                         Colorize(ColorChallenge, FALSE);
2634                         curColor = ColorChallenge;
2635                     }
2636                     loggedOn = TRUE;
2637                     continue;
2638                 }
2639
2640                 if (looking_at(buf, &i, "* offers you") ||
2641                     looking_at(buf, &i, "* offers to be") ||
2642                     looking_at(buf, &i, "* would like to") ||
2643                     looking_at(buf, &i, "* requests to") ||
2644                     looking_at(buf, &i, "Your opponent offers") ||
2645                     looking_at(buf, &i, "Your opponent requests")) {
2646
2647                     if (appData.colorize) {
2648                         if (oldi > next_out) {
2649                             SendToPlayer(&buf[next_out], oldi - next_out);
2650                             next_out = oldi;
2651                         }
2652                         Colorize(ColorRequest, FALSE);
2653                         curColor = ColorRequest;
2654                     }
2655                     continue;
2656                 }
2657
2658                 if (looking_at(buf, &i, "* (*) seeking")) {
2659                     if (appData.colorize) {
2660                         if (oldi > next_out) {
2661                             SendToPlayer(&buf[next_out], oldi - next_out);
2662                             next_out = oldi;
2663                         }
2664                         Colorize(ColorSeek, FALSE);
2665                         curColor = ColorSeek;
2666                     }
2667                     continue;
2668             }
2669
2670             if (looking_at(buf, &i, "\\   ")) {
2671                 if (prevColor != ColorNormal) {
2672                     if (oldi > next_out) {
2673                         SendToPlayer(&buf[next_out], oldi - next_out);
2674                         next_out = oldi;
2675                     }
2676                     Colorize(prevColor, TRUE);
2677                     curColor = prevColor;
2678                 }
2679                 if (savingComment) {
2680                     parse_pos = i - oldi;
2681                     memcpy(parse, &buf[oldi], parse_pos);
2682                     parse[parse_pos] = NULLCHAR;
2683                     started = STARTED_COMMENT;
2684                 } else {
2685                     started = STARTED_CHATTER;
2686                 }
2687                 continue;
2688             }
2689
2690             if (looking_at(buf, &i, "Black Strength :") ||
2691                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2692                 looking_at(buf, &i, "<10>") ||
2693                 looking_at(buf, &i, "#@#")) {
2694                 /* Wrong board style */
2695                 loggedOn = TRUE;
2696                 SendToICS(ics_prefix);
2697                 SendToICS("set style 12\n");
2698                 SendToICS(ics_prefix);
2699                 SendToICS("refresh\n");
2700                 continue;
2701             }
2702
2703             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2704                 ICSInitScript();
2705                 have_sent_ICS_logon = 1;
2706                 continue;
2707             }
2708
2709             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2710                 (looking_at(buf, &i, "\n<12> ") ||
2711                  looking_at(buf, &i, "<12> "))) {
2712                 loggedOn = TRUE;
2713                 if (oldi > next_out) {
2714                     SendToPlayer(&buf[next_out], oldi - next_out);
2715                 }
2716                 next_out = i;
2717                 started = STARTED_BOARD;
2718                 parse_pos = 0;
2719                 continue;
2720             }
2721
2722             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2723                 looking_at(buf, &i, "<b1> ")) {
2724                 if (oldi > next_out) {
2725                     SendToPlayer(&buf[next_out], oldi - next_out);
2726                 }
2727                 next_out = i;
2728                 started = STARTED_HOLDINGS;
2729                 parse_pos = 0;
2730                 continue;
2731             }
2732
2733             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2734                 loggedOn = TRUE;
2735                 /* Header for a move list -- first line */
2736
2737                 switch (ics_getting_history) {
2738                   case H_FALSE:
2739                     switch (gameMode) {
2740                       case IcsIdle:
2741                       case BeginningOfGame:
2742                         /* User typed "moves" or "oldmoves" while we
2743                            were idle.  Pretend we asked for these
2744                            moves and soak them up so user can step
2745                            through them and/or save them.
2746                            */
2747                         Reset(FALSE, TRUE);
2748                         gameMode = IcsObserving;
2749                         ModeHighlight();
2750                         ics_gamenum = -1;
2751                         ics_getting_history = H_GOT_UNREQ_HEADER;
2752                         break;
2753                       case EditGame: /*?*/
2754                       case EditPosition: /*?*/
2755                         /* Should above feature work in these modes too? */
2756                         /* For now it doesn't */
2757                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2758                         break;
2759                       default:
2760                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2761                         break;
2762                     }
2763                     break;
2764                   case H_REQUESTED:
2765                     /* Is this the right one? */
2766                     if (gameInfo.white && gameInfo.black &&
2767                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2768                         strcmp(gameInfo.black, star_match[2]) == 0) {
2769                         /* All is well */
2770                         ics_getting_history = H_GOT_REQ_HEADER;
2771                     }
2772                     break;
2773                   case H_GOT_REQ_HEADER:
2774                   case H_GOT_UNREQ_HEADER:
2775                   case H_GOT_UNWANTED_HEADER:
2776                   case H_GETTING_MOVES:
2777                     /* Should not happen */
2778                     DisplayError(_("Error gathering move list: two headers"), 0);
2779                     ics_getting_history = H_FALSE;
2780                     break;
2781                 }
2782
2783                 /* Save player ratings into gameInfo if needed */
2784                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2785                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2786                     (gameInfo.whiteRating == -1 ||
2787                      gameInfo.blackRating == -1)) {
2788
2789                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2790                     gameInfo.blackRating = string_to_rating(star_match[3]);
2791                     if (appData.debugMode)
2792                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2793                               gameInfo.whiteRating, gameInfo.blackRating);
2794                 }
2795                 continue;
2796             }
2797
2798             if (looking_at(buf, &i,
2799               "* * match, initial time: * minute*, increment: * second")) {
2800                 /* Header for a move list -- second line */
2801                 /* Initial board will follow if this is a wild game */
2802                 if (gameInfo.event != NULL) free(gameInfo.event);
2803                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2804                 gameInfo.event = StrSave(str);
2805                 /* [HGM] we switched variant. Translate boards if needed. */
2806                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2807                 continue;
2808             }
2809
2810             if (looking_at(buf, &i, "Move  ")) {
2811                 /* Beginning of a move list */
2812                 switch (ics_getting_history) {
2813                   case H_FALSE:
2814                     /* Normally should not happen */
2815                     /* Maybe user hit reset while we were parsing */
2816                     break;
2817                   case H_REQUESTED:
2818                     /* Happens if we are ignoring a move list that is not
2819                      * the one we just requested.  Common if the user
2820                      * tries to observe two games without turning off
2821                      * getMoveList */
2822                     break;
2823                   case H_GETTING_MOVES:
2824                     /* Should not happen */
2825                     DisplayError(_("Error gathering move list: nested"), 0);
2826                     ics_getting_history = H_FALSE;
2827                     break;
2828                   case H_GOT_REQ_HEADER:
2829                     ics_getting_history = H_GETTING_MOVES;
2830                     started = STARTED_MOVES;
2831                     parse_pos = 0;
2832                     if (oldi > next_out) {
2833                         SendToPlayer(&buf[next_out], oldi - next_out);
2834                     }
2835                     break;
2836                   case H_GOT_UNREQ_HEADER:
2837                     ics_getting_history = H_GETTING_MOVES;
2838                     started = STARTED_MOVES_NOHIDE;
2839                     parse_pos = 0;
2840                     break;
2841                   case H_GOT_UNWANTED_HEADER:
2842                     ics_getting_history = H_FALSE;
2843                     break;
2844                 }
2845                 continue;
2846             }
2847
2848             if (looking_at(buf, &i, "% ") ||
2849                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2850                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2851                 savingComment = FALSE;
2852                 switch (started) {
2853                   case STARTED_MOVES:
2854                   case STARTED_MOVES_NOHIDE:
2855                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2856                     parse[parse_pos + i - oldi] = NULLCHAR;
2857                     ParseGameHistory(parse);
2858 #if ZIPPY
2859                     if (appData.zippyPlay && first.initDone) {
2860                         FeedMovesToProgram(&first, forwardMostMove);
2861                         if (gameMode == IcsPlayingWhite) {
2862                             if (WhiteOnMove(forwardMostMove)) {
2863                                 if (first.sendTime) {
2864                                   if (first.useColors) {
2865                                     SendToProgram("black\n", &first);
2866                                   }
2867                                   SendTimeRemaining(&first, TRUE);
2868                                 }
2869                                 if (first.useColors) {
2870                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2871                                 }
2872                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2873                                 first.maybeThinking = TRUE;
2874                             } else {
2875                                 if (first.usePlayother) {
2876                                   if (first.sendTime) {
2877                                     SendTimeRemaining(&first, TRUE);
2878                                   }
2879                                   SendToProgram("playother\n", &first);
2880                                   firstMove = FALSE;
2881                                 } else {
2882                                   firstMove = TRUE;
2883                                 }
2884                             }
2885                         } else if (gameMode == IcsPlayingBlack) {
2886                             if (!WhiteOnMove(forwardMostMove)) {
2887                                 if (first.sendTime) {
2888                                   if (first.useColors) {
2889                                     SendToProgram("white\n", &first);
2890                                   }
2891                                   SendTimeRemaining(&first, FALSE);
2892                                 }
2893                                 if (first.useColors) {
2894                                   SendToProgram("black\n", &first);
2895                                 }
2896                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2897                                 first.maybeThinking = TRUE;
2898                             } else {
2899                                 if (first.usePlayother) {
2900                                   if (first.sendTime) {
2901                                     SendTimeRemaining(&first, FALSE);
2902                                   }
2903                                   SendToProgram("playother\n", &first);
2904                                   firstMove = FALSE;
2905                                 } else {
2906                                   firstMove = TRUE;
2907                                 }
2908                             }
2909                         }
2910                     }
2911 #endif
2912                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2913                         /* Moves came from oldmoves or moves command
2914                            while we weren't doing anything else.
2915                            */
2916                         currentMove = forwardMostMove;
2917                         ClearHighlights();/*!!could figure this out*/
2918                         flipView = appData.flipView;
2919                         DrawPosition(TRUE, boards[currentMove]);
2920                         DisplayBothClocks();
2921                         sprintf(str, "%s vs. %s",
2922                                 gameInfo.white, gameInfo.black);
2923                         DisplayTitle(str);
2924                         gameMode = IcsIdle;
2925                     } else {
2926                         /* Moves were history of an active game */
2927                         if (gameInfo.resultDetails != NULL) {
2928                             free(gameInfo.resultDetails);
2929                             gameInfo.resultDetails = NULL;
2930                         }
2931                     }
2932                     HistorySet(parseList, backwardMostMove,
2933                                forwardMostMove, currentMove-1);
2934                     DisplayMove(currentMove - 1);
2935                     if (started == STARTED_MOVES) next_out = i;
2936                     started = STARTED_NONE;
2937                     ics_getting_history = H_FALSE;
2938                     break;
2939
2940                   case STARTED_OBSERVE:
2941                     started = STARTED_NONE;
2942                     SendToICS(ics_prefix);
2943                     SendToICS("refresh\n");
2944                     break;
2945
2946                   default:
2947                     break;
2948                 }
2949                 if(bookHit) { // [HGM] book: simulate book reply
2950                     static char bookMove[MSG_SIZ]; // a bit generous?
2951
2952                     programStats.nodes = programStats.depth = programStats.time =
2953                     programStats.score = programStats.got_only_move = 0;
2954                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2955
2956                     strcpy(bookMove, "move ");
2957                     strcat(bookMove, bookHit);
2958                     HandleMachineMove(bookMove, &first);
2959                 }
2960                 continue;
2961             }
2962
2963             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2964                  started == STARTED_HOLDINGS ||
2965                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2966                 /* Accumulate characters in move list or board */
2967                 parse[parse_pos++] = buf[i];
2968             }
2969
2970             /* Start of game messages.  Mostly we detect start of game
2971                when the first board image arrives.  On some versions
2972                of the ICS, though, we need to do a "refresh" after starting
2973                to observe in order to get the current board right away. */
2974             if (looking_at(buf, &i, "Adding game * to observation list")) {
2975                 started = STARTED_OBSERVE;
2976                 continue;
2977             }
2978
2979             /* Handle auto-observe */
2980             if (appData.autoObserve &&
2981                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2982                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2983                 char *player;
2984                 /* Choose the player that was highlighted, if any. */
2985                 if (star_match[0][0] == '\033' ||
2986                     star_match[1][0] != '\033') {
2987                     player = star_match[0];
2988                 } else {
2989                     player = star_match[2];
2990                 }
2991                 sprintf(str, "%sobserve %s\n",
2992                         ics_prefix, StripHighlightAndTitle(player));
2993                 SendToICS(str);
2994
2995                 /* Save ratings from notify string */
2996                 strcpy(player1Name, star_match[0]);
2997                 player1Rating = string_to_rating(star_match[1]);
2998                 strcpy(player2Name, star_match[2]);
2999                 player2Rating = string_to_rating(star_match[3]);
3000
3001                 if (appData.debugMode)
3002                   fprintf(debugFP,
3003                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3004                           player1Name, player1Rating,
3005                           player2Name, player2Rating);
3006
3007                 continue;
3008             }
3009
3010             /* Deal with automatic examine mode after a game,
3011                and with IcsObserving -> IcsExamining transition */
3012             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3013                 looking_at(buf, &i, "has made you an examiner of game *")) {
3014
3015                 int gamenum = atoi(star_match[0]);
3016                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3017                     gamenum == ics_gamenum) {
3018                     /* We were already playing or observing this game;
3019                        no need to refetch history */
3020                     gameMode = IcsExamining;
3021                     if (pausing) {
3022                         pauseExamForwardMostMove = forwardMostMove;
3023                     } else if (currentMove < forwardMostMove) {
3024                         ForwardInner(forwardMostMove);
3025                     }
3026                 } else {
3027                     /* I don't think this case really can happen */
3028                     SendToICS(ics_prefix);
3029                     SendToICS("refresh\n");
3030                 }
3031                 continue;
3032             }
3033
3034             /* Error messages */
3035 //          if (ics_user_moved) {
3036             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3037                 if (looking_at(buf, &i, "Illegal move") ||
3038                     looking_at(buf, &i, "Not a legal move") ||
3039                     looking_at(buf, &i, "Your king is in check") ||
3040                     looking_at(buf, &i, "It isn't your turn") ||
3041                     looking_at(buf, &i, "It is not your move")) {
3042                     /* Illegal move */
3043                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3044                         currentMove = --forwardMostMove;
3045                         DisplayMove(currentMove - 1); /* before DMError */
3046                         DrawPosition(FALSE, boards[currentMove]);
3047                         SwitchClocks();
3048                         DisplayBothClocks();
3049                     }
3050                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3051                     ics_user_moved = 0;
3052                     continue;
3053                 }
3054             }
3055
3056             if (looking_at(buf, &i, "still have time") ||
3057                 looking_at(buf, &i, "not out of time") ||
3058                 looking_at(buf, &i, "either player is out of time") ||
3059                 looking_at(buf, &i, "has timeseal; checking")) {
3060                 /* We must have called his flag a little too soon */
3061                 whiteFlag = blackFlag = FALSE;
3062                 continue;
3063             }
3064
3065             if (looking_at(buf, &i, "added * seconds to") ||
3066                 looking_at(buf, &i, "seconds were added to")) {
3067                 /* Update the clocks */
3068                 SendToICS(ics_prefix);
3069                 SendToICS("refresh\n");
3070                 continue;
3071             }
3072
3073             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3074                 ics_clock_paused = TRUE;
3075                 StopClocks();
3076                 continue;
3077             }
3078
3079             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3080                 ics_clock_paused = FALSE;
3081                 StartClocks();
3082                 continue;
3083             }
3084
3085             /* Grab player ratings from the Creating: message.
3086                Note we have to check for the special case when
3087                the ICS inserts things like [white] or [black]. */
3088             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3089                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3090                 /* star_matches:
3091                    0    player 1 name (not necessarily white)
3092                    1    player 1 rating
3093                    2    empty, white, or black (IGNORED)
3094                    3    player 2 name (not necessarily black)
3095                    4    player 2 rating
3096
3097                    The names/ratings are sorted out when the game
3098                    actually starts (below).
3099                 */
3100                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3101                 player1Rating = string_to_rating(star_match[1]);
3102                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3103                 player2Rating = string_to_rating(star_match[4]);
3104
3105                 if (appData.debugMode)
3106                   fprintf(debugFP,
3107                           "Ratings from 'Creating:' %s %d, %s %d\n",
3108                           player1Name, player1Rating,
3109                           player2Name, player2Rating);
3110
3111                 continue;
3112             }
3113
3114             /* Improved generic start/end-of-game messages */
3115             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3116                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3117                 /* If tkind == 0: */
3118                 /* star_match[0] is the game number */
3119                 /*           [1] is the white player's name */
3120                 /*           [2] is the black player's name */
3121                 /* For end-of-game: */
3122                 /*           [3] is the reason for the game end */
3123                 /*           [4] is a PGN end game-token, preceded by " " */
3124                 /* For start-of-game: */
3125                 /*           [3] begins with "Creating" or "Continuing" */
3126                 /*           [4] is " *" or empty (don't care). */
3127                 int gamenum = atoi(star_match[0]);
3128                 char *whitename, *blackname, *why, *endtoken;
3129                 ChessMove endtype = (ChessMove) 0;
3130
3131                 if (tkind == 0) {
3132                   whitename = star_match[1];
3133                   blackname = star_match[2];
3134                   why = star_match[3];
3135                   endtoken = star_match[4];
3136                 } else {
3137                   whitename = star_match[1];
3138                   blackname = star_match[3];
3139                   why = star_match[5];
3140                   endtoken = star_match[6];
3141                 }
3142
3143                 /* Game start messages */
3144                 if (strncmp(why, "Creating ", 9) == 0 ||
3145                     strncmp(why, "Continuing ", 11) == 0) {
3146                     gs_gamenum = gamenum;
3147                     strcpy(gs_kind, strchr(why, ' ') + 1);
3148 #if ZIPPY
3149                     if (appData.zippyPlay) {
3150                         ZippyGameStart(whitename, blackname);
3151                     }
3152 #endif /*ZIPPY*/
3153                     continue;
3154                 }
3155
3156                 /* Game end messages */
3157                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3158                     ics_gamenum != gamenum) {
3159                     continue;
3160                 }
3161                 while (endtoken[0] == ' ') endtoken++;
3162                 switch (endtoken[0]) {
3163                   case '*':
3164                   default:
3165                     endtype = GameUnfinished;
3166                     break;
3167                   case '0':
3168                     endtype = BlackWins;
3169                     break;
3170                   case '1':
3171                     if (endtoken[1] == '/')
3172                       endtype = GameIsDrawn;
3173                     else
3174                       endtype = WhiteWins;
3175                     break;
3176                 }
3177                 GameEnds(endtype, why, GE_ICS);
3178 #if ZIPPY
3179                 if (appData.zippyPlay && first.initDone) {
3180                     ZippyGameEnd(endtype, why);
3181                     if (first.pr == NULL) {
3182                       /* Start the next process early so that we'll
3183                          be ready for the next challenge */
3184                       StartChessProgram(&first);
3185                     }
3186                     /* Send "new" early, in case this command takes
3187                        a long time to finish, so that we'll be ready
3188                        for the next challenge. */
3189                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3190                     Reset(TRUE, TRUE);
3191                 }
3192 #endif /*ZIPPY*/
3193                 continue;
3194             }
3195
3196             if (looking_at(buf, &i, "Removing game * from observation") ||
3197                 looking_at(buf, &i, "no longer observing game *") ||
3198                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3199                 if (gameMode == IcsObserving &&
3200                     atoi(star_match[0]) == ics_gamenum)
3201                   {
3202                       /* icsEngineAnalyze */
3203                       if (appData.icsEngineAnalyze) {
3204                             ExitAnalyzeMode();
3205                             ModeHighlight();
3206                       }
3207                       StopClocks();
3208                       gameMode = IcsIdle;
3209                       ics_gamenum = -1;
3210                       ics_user_moved = FALSE;
3211                   }
3212                 continue;
3213             }
3214
3215             if (looking_at(buf, &i, "no longer examining game *")) {
3216                 if (gameMode == IcsExamining &&
3217                     atoi(star_match[0]) == ics_gamenum)
3218                   {
3219                       gameMode = IcsIdle;
3220                       ics_gamenum = -1;
3221                       ics_user_moved = FALSE;
3222                   }
3223                 continue;
3224             }
3225
3226             /* Advance leftover_start past any newlines we find,
3227                so only partial lines can get reparsed */
3228             if (looking_at(buf, &i, "\n")) {
3229                 prevColor = curColor;
3230                 if (curColor != ColorNormal) {
3231                     if (oldi > next_out) {
3232                         SendToPlayer(&buf[next_out], oldi - next_out);
3233                         next_out = oldi;
3234                     }
3235                     Colorize(ColorNormal, FALSE);
3236                     curColor = ColorNormal;
3237                 }
3238                 if (started == STARTED_BOARD) {
3239                     started = STARTED_NONE;
3240                     parse[parse_pos] = NULLCHAR;
3241                     ParseBoard12(parse);
3242                     ics_user_moved = 0;
3243
3244                     /* Send premove here */
3245                     if (appData.premove) {
3246                       char str[MSG_SIZ];
3247                       if (currentMove == 0 &&
3248                           gameMode == IcsPlayingWhite &&
3249                           appData.premoveWhite) {
3250                         sprintf(str, "%s\n", appData.premoveWhiteText);
3251                         if (appData.debugMode)
3252                           fprintf(debugFP, "Sending premove:\n");
3253                         SendToICS(str);
3254                       } else if (currentMove == 1 &&
3255                                  gameMode == IcsPlayingBlack &&
3256                                  appData.premoveBlack) {
3257                         sprintf(str, "%s\n", appData.premoveBlackText);
3258                         if (appData.debugMode)
3259                           fprintf(debugFP, "Sending premove:\n");
3260                         SendToICS(str);
3261                       } else if (gotPremove) {
3262                         gotPremove = 0;
3263                         ClearPremoveHighlights();
3264                         if (appData.debugMode)
3265                           fprintf(debugFP, "Sending premove:\n");
3266                           UserMoveEvent(premoveFromX, premoveFromY,
3267                                         premoveToX, premoveToY,
3268                                         premovePromoChar);
3269                       }
3270                     }
3271
3272                     /* Usually suppress following prompt */
3273                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3274                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3275                         if (looking_at(buf, &i, "*% ")) {
3276                             savingComment = FALSE;
3277                         }
3278                     }
3279                     next_out = i;
3280                 } else if (started == STARTED_HOLDINGS) {
3281                     int gamenum;
3282                     char new_piece[MSG_SIZ];
3283                     started = STARTED_NONE;
3284                     parse[parse_pos] = NULLCHAR;
3285                     if (appData.debugMode)
3286                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3287                                                         parse, currentMove);
3288                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3289                         gamenum == ics_gamenum) {
3290                         if (gameInfo.variant == VariantNormal) {
3291                           /* [HGM] We seem to switch variant during a game!
3292                            * Presumably no holdings were displayed, so we have
3293                            * to move the position two files to the right to
3294                            * create room for them!
3295                            */
3296                           VariantClass newVariant;
3297                           switch(gameInfo.boardWidth) { // base guess on board width
3298                                 case 9:  newVariant = VariantShogi; break;
3299                                 case 10: newVariant = VariantGreat; break;
3300                                 default: newVariant = VariantCrazyhouse; break;
3301                           }
3302                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3303                           /* Get a move list just to see the header, which
3304                              will tell us whether this is really bug or zh */
3305                           if (ics_getting_history == H_FALSE) {
3306                             ics_getting_history = H_REQUESTED;
3307                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3308                             SendToICS(str);
3309                           }
3310                         }
3311                         new_piece[0] = NULLCHAR;
3312                         sscanf(parse, "game %d white [%s black [%s <- %s",
3313                                &gamenum, white_holding, black_holding,
3314                                new_piece);
3315                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3316                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3317                         /* [HGM] copy holdings to board holdings area */
3318                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3319                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3320                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3321 #if ZIPPY
3322                         if (appData.zippyPlay && first.initDone) {
3323                             ZippyHoldings(white_holding, black_holding,
3324                                           new_piece);
3325                         }
3326 #endif /*ZIPPY*/
3327                         if (tinyLayout || smallLayout) {
3328                             char wh[16], bh[16];
3329                             PackHolding(wh, white_holding);
3330                             PackHolding(bh, black_holding);
3331                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3332                                     gameInfo.white, gameInfo.black);
3333                         } else {
3334                             sprintf(str, "%s [%s] vs. %s [%s]",
3335                                     gameInfo.white, white_holding,
3336                                     gameInfo.black, black_holding);
3337                         }
3338
3339                         DrawPosition(FALSE, boards[currentMove]);
3340                         DisplayTitle(str);
3341                     }
3342                     /* Suppress following prompt */
3343                     if (looking_at(buf, &i, "*% ")) {
3344                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3345                         savingComment = FALSE;
3346                     }
3347                     next_out = i;
3348                 }
3349                 continue;
3350             }
3351
3352             i++;                /* skip unparsed character and loop back */
3353         }
3354
3355         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3356             started != STARTED_HOLDINGS && i > next_out) {
3357             SendToPlayer(&buf[next_out], i - next_out);
3358             next_out = i;
3359         }
3360         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3361
3362         leftover_len = buf_len - leftover_start;
3363         /* if buffer ends with something we couldn't parse,
3364            reparse it after appending the next read */
3365
3366     } else if (count == 0) {
3367         RemoveInputSource(isr);
3368         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3369     } else {
3370         DisplayFatalError(_("Error reading from ICS"), error, 1);
3371     }
3372 }
3373
3374
3375 /* Board style 12 looks like this:
3376
3377    <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
3378
3379  * The "<12> " is stripped before it gets to this routine.  The two
3380  * trailing 0's (flip state and clock ticking) are later addition, and
3381  * some chess servers may not have them, or may have only the first.
3382  * Additional trailing fields may be added in the future.
3383  */
3384
3385 #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"
3386
3387 #define RELATION_OBSERVING_PLAYED    0
3388 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3389 #define RELATION_PLAYING_MYMOVE      1
3390 #define RELATION_PLAYING_NOTMYMOVE  -1
3391 #define RELATION_EXAMINING           2
3392 #define RELATION_ISOLATED_BOARD     -3
3393 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3394
3395 void
3396 ParseBoard12(string)
3397      char *string;
3398 {
3399     GameMode newGameMode;
3400     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3401     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3402     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3403     char to_play, board_chars[200];
3404     char move_str[500], str[500], elapsed_time[500];
3405     char black[32], white[32];
3406     Board board;
3407     int prevMove = currentMove;
3408     int ticking = 2;
3409     ChessMove moveType;
3410     int fromX, fromY, toX, toY;
3411     char promoChar;
3412     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3413     char *bookHit = NULL; // [HGM] book
3414     Boolean weird = FALSE, reqFlag = FALSE;
3415
3416     fromX = fromY = toX = toY = -1;
3417
3418     newGame = FALSE;
3419
3420     if (appData.debugMode)
3421       fprintf(debugFP, _("Parsing board: %s\n"), string);
3422
3423     move_str[0] = NULLCHAR;
3424     elapsed_time[0] = NULLCHAR;
3425     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3426         int  i = 0, j;
3427         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3428             if(string[i] == ' ') { ranks++; files = 0; }
3429             else files++;
3430             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3431             i++;
3432         }
3433         for(j = 0; j <i; j++) board_chars[j] = string[j];
3434         board_chars[i] = '\0';
3435         string += i + 1;
3436     }
3437     n = sscanf(string, PATTERN, &to_play, &double_push,
3438                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3439                &gamenum, white, black, &relation, &basetime, &increment,
3440                &white_stren, &black_stren, &white_time, &black_time,
3441                &moveNum, str, elapsed_time, move_str, &ics_flip,
3442                &ticking);
3443
3444     if (n < 21) {
3445         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3446         DisplayError(str, 0);
3447         return;
3448     }
3449
3450     /* Convert the move number to internal form */
3451     moveNum = (moveNum - 1) * 2;
3452     if (to_play == 'B') moveNum++;
3453     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3454       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3455                         0, 1);
3456       return;
3457     }
3458
3459     switch (relation) {
3460       case RELATION_OBSERVING_PLAYED:
3461       case RELATION_OBSERVING_STATIC:
3462         if (gamenum == -1) {
3463             /* Old ICC buglet */
3464             relation = RELATION_OBSERVING_STATIC;
3465         }
3466         newGameMode = IcsObserving;
3467         break;
3468       case RELATION_PLAYING_MYMOVE:
3469       case RELATION_PLAYING_NOTMYMOVE:
3470         newGameMode =
3471           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3472             IcsPlayingWhite : IcsPlayingBlack;
3473         break;
3474       case RELATION_EXAMINING:
3475         newGameMode = IcsExamining;
3476         break;
3477       case RELATION_ISOLATED_BOARD:
3478       default:
3479         /* Just display this board.  If user was doing something else,
3480            we will forget about it until the next board comes. */
3481         newGameMode = IcsIdle;
3482         break;
3483       case RELATION_STARTING_POSITION:
3484         newGameMode = gameMode;
3485         break;
3486     }
3487
3488     /* Modify behavior for initial board display on move listing
3489        of wild games.
3490        */
3491     switch (ics_getting_history) {
3492       case H_FALSE:
3493       case H_REQUESTED:
3494         break;
3495       case H_GOT_REQ_HEADER:
3496       case H_GOT_UNREQ_HEADER:
3497         /* This is the initial position of the current game */
3498         gamenum = ics_gamenum;
3499         moveNum = 0;            /* old ICS bug workaround */
3500         if (to_play == 'B') {
3501           startedFromSetupPosition = TRUE;
3502           blackPlaysFirst = TRUE;
3503           moveNum = 1;
3504           if (forwardMostMove == 0) forwardMostMove = 1;
3505           if (backwardMostMove == 0) backwardMostMove = 1;
3506           if (currentMove == 0) currentMove = 1;
3507         }
3508         newGameMode = gameMode;
3509         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3510         break;
3511       case H_GOT_UNWANTED_HEADER:
3512         /* This is an initial board that we don't want */
3513         return;
3514       case H_GETTING_MOVES:
3515         /* Should not happen */
3516         DisplayError(_("Error gathering move list: extra board"), 0);
3517         ics_getting_history = H_FALSE;
3518         return;
3519     }
3520
3521    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3522                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3523      /* [HGM] We seem to have switched variant unexpectedly
3524       * Try to guess new variant from board size
3525       */
3526           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3527           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3528           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3529           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3530           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3531           if(!weird) newVariant = VariantNormal;
3532           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3533           /* Get a move list just to see the header, which
3534              will tell us whether this is really bug or zh */
3535           if (ics_getting_history == H_FALSE) {
3536             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3537             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3538             SendToICS(str);
3539           }
3540     }
3541     
3542     /* Take action if this is the first board of a new game, or of a
3543        different game than is currently being displayed.  */
3544     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3545         relation == RELATION_ISOLATED_BOARD) {
3546
3547         /* Forget the old game and get the history (if any) of the new one */
3548         if (gameMode != BeginningOfGame) {
3549           Reset(TRUE, TRUE);
3550         }
3551         newGame = TRUE;
3552         if (appData.autoRaiseBoard) BoardToTop();
3553         prevMove = -3;
3554         if (gamenum == -1) {
3555             newGameMode = IcsIdle;
3556         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3557                    appData.getMoveList && !reqFlag) {
3558             /* Need to get game history */
3559             ics_getting_history = H_REQUESTED;
3560             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3561             SendToICS(str);
3562         }
3563
3564         /* Initially flip the board to have black on the bottom if playing
3565            black or if the ICS flip flag is set, but let the user change
3566            it with the Flip View button. */
3567         flipView = appData.autoFlipView ?
3568           (newGameMode == IcsPlayingBlack) || ics_flip :
3569           appData.flipView;
3570
3571         /* Done with values from previous mode; copy in new ones */
3572         gameMode = newGameMode;
3573         ModeHighlight();
3574         ics_gamenum = gamenum;
3575         if (gamenum == gs_gamenum) {
3576             int klen = strlen(gs_kind);
3577             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3578             sprintf(str, "ICS %s", gs_kind);
3579             gameInfo.event = StrSave(str);
3580         } else {
3581             gameInfo.event = StrSave("ICS game");
3582         }
3583         gameInfo.site = StrSave(appData.icsHost);
3584         gameInfo.date = PGNDate();
3585         gameInfo.round = StrSave("-");
3586         gameInfo.white = StrSave(white);
3587         gameInfo.black = StrSave(black);
3588         timeControl = basetime * 60 * 1000;
3589         timeControl_2 = 0;
3590         timeIncrement = increment * 1000;
3591         movesPerSession = 0;
3592         gameInfo.timeControl = TimeControlTagValue();
3593         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3594   if (appData.debugMode) {
3595     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3596     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3597     setbuf(debugFP, NULL);
3598   }
3599
3600         gameInfo.outOfBook = NULL;
3601
3602         /* Do we have the ratings? */
3603         if (strcmp(player1Name, white) == 0 &&
3604             strcmp(player2Name, black) == 0) {
3605             if (appData.debugMode)
3606               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3607                       player1Rating, player2Rating);
3608             gameInfo.whiteRating = player1Rating;
3609             gameInfo.blackRating = player2Rating;
3610         } else if (strcmp(player2Name, white) == 0 &&
3611                    strcmp(player1Name, black) == 0) {
3612             if (appData.debugMode)
3613               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3614                       player2Rating, player1Rating);
3615             gameInfo.whiteRating = player2Rating;
3616             gameInfo.blackRating = player1Rating;
3617         }
3618         player1Name[0] = player2Name[0] = NULLCHAR;
3619
3620         /* Silence shouts if requested */
3621         if (appData.quietPlay &&
3622             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3623             SendToICS(ics_prefix);
3624             SendToICS("set shout 0\n");
3625         }
3626     }
3627
3628     /* Deal with midgame name changes */
3629     if (!newGame) {
3630         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3631             if (gameInfo.white) free(gameInfo.white);
3632             gameInfo.white = StrSave(white);
3633         }
3634         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3635             if (gameInfo.black) free(gameInfo.black);
3636             gameInfo.black = StrSave(black);
3637         }
3638     }
3639
3640     /* Throw away game result if anything actually changes in examine mode */
3641     if (gameMode == IcsExamining && !newGame) {
3642         gameInfo.result = GameUnfinished;
3643         if (gameInfo.resultDetails != NULL) {
3644             free(gameInfo.resultDetails);
3645             gameInfo.resultDetails = NULL;
3646         }
3647     }
3648
3649     /* In pausing && IcsExamining mode, we ignore boards coming
3650        in if they are in a different variation than we are. */
3651     if (pauseExamInvalid) return;
3652     if (pausing && gameMode == IcsExamining) {
3653         if (moveNum <= pauseExamForwardMostMove) {
3654             pauseExamInvalid = TRUE;
3655             forwardMostMove = pauseExamForwardMostMove;
3656             return;
3657         }
3658     }
3659
3660   if (appData.debugMode) {
3661     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3662   }
3663     /* Parse the board */
3664     for (k = 0; k < ranks; k++) {
3665       for (j = 0; j < files; j++)
3666         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3667       if(gameInfo.holdingsWidth > 1) {
3668            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3669            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3670       }
3671     }
3672     CopyBoard(boards[moveNum], board);
3673     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3674     if (moveNum == 0) {
3675         startedFromSetupPosition =
3676           !CompareBoards(board, initialPosition);
3677         if(startedFromSetupPosition)
3678             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3679     }
3680
3681     /* [HGM] Set castling rights. Take the outermost Rooks,
3682        to make it also work for FRC opening positions. Note that board12
3683        is really defective for later FRC positions, as it has no way to
3684        indicate which Rook can castle if they are on the same side of King.
3685        For the initial position we grant rights to the outermost Rooks,
3686        and remember thos rights, and we then copy them on positions
3687        later in an FRC game. This means WB might not recognize castlings with
3688        Rooks that have moved back to their original position as illegal,
3689        but in ICS mode that is not its job anyway.
3690     */
3691     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3692     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3693
3694         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3695             if(board[0][i] == WhiteRook) j = i;
3696         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3697         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3698             if(board[0][i] == WhiteRook) j = i;
3699         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3700         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3701             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3702         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3703         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3704             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3705         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3706
3707         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3708         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3709             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3710         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3711             if(board[BOARD_HEIGHT-1][k] == bKing)
3712                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3713     } else { int r;
3714         r = boards[moveNum][CASTLING][0] = initialRights[0];
3715         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3716         r = boards[moveNum][CASTLING][1] = initialRights[1];
3717         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3718         r = boards[moveNum][CASTLING][3] = initialRights[3];
3719         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3720         r = boards[moveNum][CASTLING][4] = initialRights[4];
3721         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3722         /* wildcastle kludge: always assume King has rights */
3723         r = boards[moveNum][CASTLING][2] = initialRights[2];
3724         r = boards[moveNum][CASTLING][5] = initialRights[5];
3725     }
3726     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3727     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3728
3729
3730     if (ics_getting_history == H_GOT_REQ_HEADER ||
3731         ics_getting_history == H_GOT_UNREQ_HEADER) {
3732         /* This was an initial position from a move list, not
3733            the current position */
3734         return;
3735     }
3736
3737     /* Update currentMove and known move number limits */
3738     newMove = newGame || moveNum > forwardMostMove;
3739
3740     if (newGame) {
3741         forwardMostMove = backwardMostMove = currentMove = moveNum;
3742         if (gameMode == IcsExamining && moveNum == 0) {
3743           /* Workaround for ICS limitation: we are not told the wild
3744              type when starting to examine a game.  But if we ask for
3745              the move list, the move list header will tell us */
3746             ics_getting_history = H_REQUESTED;
3747             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3748             SendToICS(str);
3749         }
3750     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3751                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3752 #if ZIPPY
3753         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3754         /* [HGM] applied this also to an engine that is silently watching        */
3755         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3756             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3757             gameInfo.variant == currentlyInitializedVariant) {
3758           takeback = forwardMostMove - moveNum;
3759           for (i = 0; i < takeback; i++) {
3760             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3761             SendToProgram("undo\n", &first);
3762           }
3763         }
3764 #endif
3765
3766         forwardMostMove = moveNum;
3767         if (!pausing || currentMove > forwardMostMove)
3768           currentMove = forwardMostMove;
3769     } else {
3770         /* New part of history that is not contiguous with old part */
3771         if (pausing && gameMode == IcsExamining) {
3772             pauseExamInvalid = TRUE;
3773             forwardMostMove = pauseExamForwardMostMove;
3774             return;
3775         }
3776         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3777 #if ZIPPY
3778             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3779                 // [HGM] when we will receive the move list we now request, it will be
3780                 // fed to the engine from the first move on. So if the engine is not
3781                 // in the initial position now, bring it there.
3782                 InitChessProgram(&first, 0);
3783             }
3784 #endif
3785             ics_getting_history = H_REQUESTED;
3786             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3787             SendToICS(str);
3788         }
3789         forwardMostMove = backwardMostMove = currentMove = moveNum;
3790     }
3791
3792     /* Update the clocks */
3793     if (strchr(elapsed_time, '.')) {
3794       /* Time is in ms */
3795       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3796       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3797     } else {
3798       /* Time is in seconds */
3799       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3800       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3801     }
3802
3803
3804 #if ZIPPY
3805     if (appData.zippyPlay && newGame &&
3806         gameMode != IcsObserving && gameMode != IcsIdle &&
3807         gameMode != IcsExamining)
3808       ZippyFirstBoard(moveNum, basetime, increment);
3809 #endif
3810
3811     /* Put the move on the move list, first converting
3812        to canonical algebraic form. */
3813     if (moveNum > 0) {
3814   if (appData.debugMode) {
3815     if (appData.debugMode) { int f = forwardMostMove;
3816         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3817                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3818                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3819     }
3820     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3821     fprintf(debugFP, "moveNum = %d\n", moveNum);
3822     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3823     setbuf(debugFP, NULL);
3824   }
3825         if (moveNum <= backwardMostMove) {
3826             /* We don't know what the board looked like before
3827                this move.  Punt. */
3828             strcpy(parseList[moveNum - 1], move_str);
3829             strcat(parseList[moveNum - 1], " ");
3830             strcat(parseList[moveNum - 1], elapsed_time);
3831             moveList[moveNum - 1][0] = NULLCHAR;
3832         } else if (strcmp(move_str, "none") == 0) {
3833             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3834             /* Again, we don't know what the board looked like;
3835                this is really the start of the game. */
3836             parseList[moveNum - 1][0] = NULLCHAR;
3837             moveList[moveNum - 1][0] = NULLCHAR;
3838             backwardMostMove = moveNum;
3839             startedFromSetupPosition = TRUE;
3840             fromX = fromY = toX = toY = -1;
3841         } else {
3842           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3843           //                 So we parse the long-algebraic move string in stead of the SAN move
3844           int valid; char buf[MSG_SIZ], *prom;
3845
3846           // str looks something like "Q/a1-a2"; kill the slash
3847           if(str[1] == '/')
3848                 sprintf(buf, "%c%s", str[0], str+2);
3849           else  strcpy(buf, str); // might be castling
3850           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3851                 strcat(buf, prom); // long move lacks promo specification!
3852           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3853                 if(appData.debugMode)
3854                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3855                 strcpy(move_str, buf);
3856           }
3857           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3858                                 &fromX, &fromY, &toX, &toY, &promoChar)
3859                || ParseOneMove(buf, moveNum - 1, &moveType,
3860                                 &fromX, &fromY, &toX, &toY, &promoChar);
3861           // end of long SAN patch
3862           if (valid) {
3863             (void) CoordsToAlgebraic(boards[moveNum - 1],
3864                                      PosFlags(moveNum - 1),
3865                                      fromY, fromX, toY, toX, promoChar,
3866                                      parseList[moveNum-1]);
3867             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3868               case MT_NONE:
3869               case MT_STALEMATE:
3870               default:
3871                 break;
3872               case MT_CHECK:
3873                 if(gameInfo.variant != VariantShogi)
3874                     strcat(parseList[moveNum - 1], "+");
3875                 break;
3876               case MT_CHECKMATE:
3877               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3878                 strcat(parseList[moveNum - 1], "#");
3879                 break;
3880             }
3881             strcat(parseList[moveNum - 1], " ");
3882             strcat(parseList[moveNum - 1], elapsed_time);
3883             /* currentMoveString is set as a side-effect of ParseOneMove */
3884             strcpy(moveList[moveNum - 1], currentMoveString);
3885             strcat(moveList[moveNum - 1], "\n");
3886           } else {
3887             /* Move from ICS was illegal!?  Punt. */
3888   if (appData.debugMode) {
3889     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3890     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3891   }
3892             strcpy(parseList[moveNum - 1], move_str);
3893             strcat(parseList[moveNum - 1], " ");
3894             strcat(parseList[moveNum - 1], elapsed_time);
3895             moveList[moveNum - 1][0] = NULLCHAR;
3896             fromX = fromY = toX = toY = -1;
3897           }
3898         }
3899   if (appData.debugMode) {
3900     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3901     setbuf(debugFP, NULL);
3902   }
3903
3904 #if ZIPPY
3905         /* Send move to chess program (BEFORE animating it). */
3906         if (appData.zippyPlay && !newGame && newMove &&
3907            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3908
3909             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3910                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3911                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3912                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3913                             move_str);
3914                     DisplayError(str, 0);
3915                 } else {
3916                     if (first.sendTime) {
3917                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3918                     }
3919                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3920                     if (firstMove && !bookHit) {
3921                         firstMove = FALSE;
3922                         if (first.useColors) {
3923                           SendToProgram(gameMode == IcsPlayingWhite ?
3924                                         "white\ngo\n" :
3925                                         "black\ngo\n", &first);
3926                         } else {
3927                           SendToProgram("go\n", &first);
3928                         }
3929                         first.maybeThinking = TRUE;
3930                     }
3931                 }
3932             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3933               if (moveList[moveNum - 1][0] == NULLCHAR) {
3934                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3935                 DisplayError(str, 0);
3936               } else {
3937                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3938                 SendMoveToProgram(moveNum - 1, &first);
3939               }
3940             }
3941         }
3942 #endif
3943     }
3944
3945     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3946         /* If move comes from a remote source, animate it.  If it
3947            isn't remote, it will have already been animated. */
3948         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3949             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3950         }
3951         if (!pausing && appData.highlightLastMove) {
3952             SetHighlights(fromX, fromY, toX, toY);
3953         }
3954     }
3955
3956     /* Start the clocks */
3957     whiteFlag = blackFlag = FALSE;
3958     appData.clockMode = !(basetime == 0 && increment == 0);
3959     if (ticking == 0) {
3960       ics_clock_paused = TRUE;
3961       StopClocks();
3962     } else if (ticking == 1) {
3963       ics_clock_paused = FALSE;
3964     }
3965     if (gameMode == IcsIdle ||
3966         relation == RELATION_OBSERVING_STATIC ||
3967         relation == RELATION_EXAMINING ||
3968         ics_clock_paused)
3969       DisplayBothClocks();
3970     else
3971       StartClocks();
3972
3973     /* Display opponents and material strengths */
3974     if (gameInfo.variant != VariantBughouse &&
3975         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3976         if (tinyLayout || smallLayout) {
3977             if(gameInfo.variant == VariantNormal)
3978                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3979                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3980                     basetime, increment);
3981             else
3982                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3983                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3984                     basetime, increment, (int) gameInfo.variant);
3985         } else {
3986             if(gameInfo.variant == VariantNormal)
3987                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3988                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3989                     basetime, increment);
3990             else
3991                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3992                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3993                     basetime, increment, VariantName(gameInfo.variant));
3994         }
3995         DisplayTitle(str);
3996   if (appData.debugMode) {
3997     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3998   }
3999     }
4000
4001
4002     /* Display the board */
4003     if (!pausing && !appData.noGUI) {
4004       if (appData.premove)
4005           if (!gotPremove ||
4006              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4007              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4008               ClearPremoveHighlights();
4009
4010       DrawPosition(FALSE, boards[currentMove]);
4011       DisplayMove(moveNum - 1);
4012       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4013             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4014               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4015         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4016       }
4017     }
4018
4019     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4020 #if ZIPPY
4021     if(bookHit) { // [HGM] book: simulate book reply
4022         static char bookMove[MSG_SIZ]; // a bit generous?
4023
4024         programStats.nodes = programStats.depth = programStats.time =
4025         programStats.score = programStats.got_only_move = 0;
4026         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4027
4028         strcpy(bookMove, "move ");
4029         strcat(bookMove, bookHit);
4030         HandleMachineMove(bookMove, &first);
4031     }
4032 #endif
4033 }
4034
4035 void
4036 GetMoveListEvent()
4037 {
4038     char buf[MSG_SIZ];
4039     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4040         ics_getting_history = H_REQUESTED;
4041         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4042         SendToICS(buf);
4043     }
4044 }
4045
4046 void
4047 AnalysisPeriodicEvent(force)
4048      int force;
4049 {
4050     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4051          && !force) || !appData.periodicUpdates)
4052       return;
4053
4054     /* Send . command to Crafty to collect stats */
4055     SendToProgram(".\n", &first);
4056
4057     /* Don't send another until we get a response (this makes
4058        us stop sending to old Crafty's which don't understand
4059        the "." command (sending illegal cmds resets node count & time,
4060        which looks bad)) */
4061     programStats.ok_to_send = 0;
4062 }
4063
4064 void ics_update_width(new_width)
4065         int new_width;
4066 {
4067         ics_printf("set width %d\n", new_width);
4068 }
4069
4070 void
4071 SendMoveToProgram(moveNum, cps)
4072      int moveNum;
4073      ChessProgramState *cps;
4074 {
4075     char buf[MSG_SIZ];
4076
4077     if (cps->useUsermove) {
4078       SendToProgram("usermove ", cps);
4079     }
4080     if (cps->useSAN) {
4081       char *space;
4082       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4083         int len = space - parseList[moveNum];
4084         memcpy(buf, parseList[moveNum], len);
4085         buf[len++] = '\n';
4086         buf[len] = NULLCHAR;
4087       } else {
4088         sprintf(buf, "%s\n", parseList[moveNum]);
4089       }
4090       SendToProgram(buf, cps);
4091     } else {
4092       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4093         AlphaRank(moveList[moveNum], 4);
4094         SendToProgram(moveList[moveNum], cps);
4095         AlphaRank(moveList[moveNum], 4); // and back
4096       } else
4097       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4098        * the engine. It would be nice to have a better way to identify castle
4099        * moves here. */
4100       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4101                                                                          && cps->useOOCastle) {
4102         int fromX = moveList[moveNum][0] - AAA;
4103         int fromY = moveList[moveNum][1] - ONE;
4104         int toX = moveList[moveNum][2] - AAA;
4105         int toY = moveList[moveNum][3] - ONE;
4106         if((boards[moveNum][fromY][fromX] == WhiteKing
4107             && boards[moveNum][toY][toX] == WhiteRook)
4108            || (boards[moveNum][fromY][fromX] == BlackKing
4109                && boards[moveNum][toY][toX] == BlackRook)) {
4110           if(toX > fromX) SendToProgram("O-O\n", cps);
4111           else SendToProgram("O-O-O\n", cps);
4112         }
4113         else SendToProgram(moveList[moveNum], cps);
4114       }
4115       else SendToProgram(moveList[moveNum], cps);
4116       /* End of additions by Tord */
4117     }
4118
4119     /* [HGM] setting up the opening has brought engine in force mode! */
4120     /*       Send 'go' if we are in a mode where machine should play. */
4121     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4122         (gameMode == TwoMachinesPlay   ||
4123 #ifdef ZIPPY
4124          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4125 #endif
4126          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4127         SendToProgram("go\n", cps);
4128   if (appData.debugMode) {
4129     fprintf(debugFP, "(extra)\n");
4130   }
4131     }
4132     setboardSpoiledMachineBlack = 0;
4133 }
4134
4135 void
4136 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4137      ChessMove moveType;
4138      int fromX, fromY, toX, toY;
4139 {
4140     char user_move[MSG_SIZ];
4141
4142     switch (moveType) {
4143       default:
4144         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4145                 (int)moveType, fromX, fromY, toX, toY);
4146         DisplayError(user_move + strlen("say "), 0);
4147         break;
4148       case WhiteKingSideCastle:
4149       case BlackKingSideCastle:
4150       case WhiteQueenSideCastleWild:
4151       case BlackQueenSideCastleWild:
4152       /* PUSH Fabien */
4153       case WhiteHSideCastleFR:
4154       case BlackHSideCastleFR:
4155       /* POP Fabien */
4156         sprintf(user_move, "o-o\n");
4157         break;
4158       case WhiteQueenSideCastle:
4159       case BlackQueenSideCastle:
4160       case WhiteKingSideCastleWild:
4161       case BlackKingSideCastleWild:
4162       /* PUSH Fabien */
4163       case WhiteASideCastleFR:
4164       case BlackASideCastleFR:
4165       /* POP Fabien */
4166         sprintf(user_move, "o-o-o\n");
4167         break;
4168       case WhitePromotionQueen:
4169       case BlackPromotionQueen:
4170       case WhitePromotionRook:
4171       case BlackPromotionRook:
4172       case WhitePromotionBishop:
4173       case BlackPromotionBishop:
4174       case WhitePromotionKnight:
4175       case BlackPromotionKnight:
4176       case WhitePromotionKing:
4177       case BlackPromotionKing:
4178       case WhitePromotionChancellor:
4179       case BlackPromotionChancellor:
4180       case WhitePromotionArchbishop:
4181       case BlackPromotionArchbishop:
4182         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4183             sprintf(user_move, "%c%c%c%c=%c\n",
4184                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4185                 PieceToChar(WhiteFerz));
4186         else if(gameInfo.variant == VariantGreat)
4187             sprintf(user_move, "%c%c%c%c=%c\n",
4188                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4189                 PieceToChar(WhiteMan));
4190         else
4191             sprintf(user_move, "%c%c%c%c=%c\n",
4192                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4193                 PieceToChar(PromoPiece(moveType)));
4194         break;
4195       case WhiteDrop:
4196       case BlackDrop:
4197         sprintf(user_move, "%c@%c%c\n",
4198                 ToUpper(PieceToChar((ChessSquare) fromX)),
4199                 AAA + toX, ONE + toY);
4200         break;
4201       case NormalMove:
4202       case WhiteCapturesEnPassant:
4203       case BlackCapturesEnPassant:
4204       case IllegalMove:  /* could be a variant we don't quite understand */
4205         sprintf(user_move, "%c%c%c%c\n",
4206                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4207         break;
4208     }
4209     SendToICS(user_move);
4210     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4211         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4212 }
4213
4214 void
4215 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4216      int rf, ff, rt, ft;
4217      char promoChar;
4218      char move[7];
4219 {
4220     if (rf == DROP_RANK) {
4221         sprintf(move, "%c@%c%c\n",
4222                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4223     } else {
4224         if (promoChar == 'x' || promoChar == NULLCHAR) {
4225             sprintf(move, "%c%c%c%c\n",
4226                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4227         } else {
4228             sprintf(move, "%c%c%c%c%c\n",
4229                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4230         }
4231     }
4232 }
4233
4234 void
4235 ProcessICSInitScript(f)
4236      FILE *f;
4237 {
4238     char buf[MSG_SIZ];
4239
4240     while (fgets(buf, MSG_SIZ, f)) {
4241         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4242     }
4243
4244     fclose(f);
4245 }
4246
4247
4248 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4249 void
4250 AlphaRank(char *move, int n)
4251 {
4252 //    char *p = move, c; int x, y;
4253
4254     if (appData.debugMode) {
4255         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4256     }
4257
4258     if(move[1]=='*' &&
4259        move[2]>='0' && move[2]<='9' &&
4260        move[3]>='a' && move[3]<='x'    ) {
4261         move[1] = '@';
4262         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4263         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4264     } else
4265     if(move[0]>='0' && move[0]<='9' &&
4266        move[1]>='a' && move[1]<='x' &&
4267        move[2]>='0' && move[2]<='9' &&
4268        move[3]>='a' && move[3]<='x'    ) {
4269         /* input move, Shogi -> normal */
4270         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4271         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4272         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4273         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4274     } else
4275     if(move[1]=='@' &&
4276        move[3]>='0' && move[3]<='9' &&
4277        move[2]>='a' && move[2]<='x'    ) {
4278         move[1] = '*';
4279         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4280         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4281     } else
4282     if(
4283        move[0]>='a' && move[0]<='x' &&
4284        move[3]>='0' && move[3]<='9' &&
4285        move[2]>='a' && move[2]<='x'    ) {
4286          /* output move, normal -> Shogi */
4287         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4288         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4289         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4290         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4291         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4292     }
4293     if (appData.debugMode) {
4294         fprintf(debugFP, "   out = '%s'\n", move);
4295     }
4296 }
4297
4298 /* Parser for moves from gnuchess, ICS, or user typein box */
4299 Boolean
4300 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4301      char *move;
4302      int moveNum;
4303      ChessMove *moveType;
4304      int *fromX, *fromY, *toX, *toY;
4305      char *promoChar;
4306 {
4307     if (appData.debugMode) {
4308         fprintf(debugFP, "move to parse: %s\n", move);
4309     }
4310     *moveType = yylexstr(moveNum, move);
4311
4312     switch (*moveType) {
4313       case WhitePromotionChancellor:
4314       case BlackPromotionChancellor:
4315       case WhitePromotionArchbishop:
4316       case BlackPromotionArchbishop:
4317       case WhitePromotionQueen:
4318       case BlackPromotionQueen:
4319       case WhitePromotionRook:
4320       case BlackPromotionRook:
4321       case WhitePromotionBishop:
4322       case BlackPromotionBishop:
4323       case WhitePromotionKnight:
4324       case BlackPromotionKnight:
4325       case WhitePromotionKing:
4326       case BlackPromotionKing:
4327       case NormalMove:
4328       case WhiteCapturesEnPassant:
4329       case BlackCapturesEnPassant:
4330       case WhiteKingSideCastle:
4331       case WhiteQueenSideCastle:
4332       case BlackKingSideCastle:
4333       case BlackQueenSideCastle:
4334       case WhiteKingSideCastleWild:
4335       case WhiteQueenSideCastleWild:
4336       case BlackKingSideCastleWild:
4337       case BlackQueenSideCastleWild:
4338       /* Code added by Tord: */
4339       case WhiteHSideCastleFR:
4340       case WhiteASideCastleFR:
4341       case BlackHSideCastleFR:
4342       case BlackASideCastleFR:
4343       /* End of code added by Tord */
4344       case IllegalMove:         /* bug or odd chess variant */
4345         *fromX = currentMoveString[0] - AAA;
4346         *fromY = currentMoveString[1] - ONE;
4347         *toX = currentMoveString[2] - AAA;
4348         *toY = currentMoveString[3] - ONE;
4349         *promoChar = currentMoveString[4];
4350         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4351             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4352     if (appData.debugMode) {
4353         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4354     }
4355             *fromX = *fromY = *toX = *toY = 0;
4356             return FALSE;
4357         }
4358         if (appData.testLegality) {
4359           return (*moveType != IllegalMove);
4360         } else {
4361           return !(fromX == fromY && toX == toY);
4362         }
4363
4364       case WhiteDrop:
4365       case BlackDrop:
4366         *fromX = *moveType == WhiteDrop ?
4367           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4368           (int) CharToPiece(ToLower(currentMoveString[0]));
4369         *fromY = DROP_RANK;
4370         *toX = currentMoveString[2] - AAA;
4371         *toY = currentMoveString[3] - ONE;
4372         *promoChar = NULLCHAR;
4373         return TRUE;
4374
4375       case AmbiguousMove:
4376       case ImpossibleMove:
4377       case (ChessMove) 0:       /* end of file */
4378       case ElapsedTime:
4379       case Comment:
4380       case PGNTag:
4381       case NAG:
4382       case WhiteWins:
4383       case BlackWins:
4384       case GameIsDrawn:
4385       default:
4386     if (appData.debugMode) {
4387         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4388     }
4389         /* bug? */
4390         *fromX = *fromY = *toX = *toY = 0;
4391         *promoChar = NULLCHAR;
4392         return FALSE;
4393     }
4394 }
4395
4396 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4397 // All positions will have equal probability, but the current method will not provide a unique
4398 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4399 #define DARK 1
4400 #define LITE 2
4401 #define ANY 3
4402
4403 int squaresLeft[4];
4404 int piecesLeft[(int)BlackPawn];
4405 int seed, nrOfShuffles;
4406
4407 void GetPositionNumber()
4408 {       // sets global variable seed
4409         int i;
4410
4411         seed = appData.defaultFrcPosition;
4412         if(seed < 0) { // randomize based on time for negative FRC position numbers
4413                 for(i=0; i<50; i++) seed += random();
4414                 seed = random() ^ random() >> 8 ^ random() << 8;
4415                 if(seed<0) seed = -seed;
4416         }
4417 }
4418
4419 int put(Board board, int pieceType, int rank, int n, int shade)
4420 // put the piece on the (n-1)-th empty squares of the given shade
4421 {
4422         int i;
4423
4424         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4425                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4426                         board[rank][i] = (ChessSquare) pieceType;
4427                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4428                         squaresLeft[ANY]--;
4429                         piecesLeft[pieceType]--;
4430                         return i;
4431                 }
4432         }
4433         return -1;
4434 }
4435
4436
4437 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4438 // calculate where the next piece goes, (any empty square), and put it there
4439 {
4440         int i;
4441
4442         i = seed % squaresLeft[shade];
4443         nrOfShuffles *= squaresLeft[shade];
4444         seed /= squaresLeft[shade];
4445         put(board, pieceType, rank, i, shade);
4446 }
4447
4448 void AddTwoPieces(Board board, int pieceType, int rank)
4449 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4450 {
4451         int i, n=squaresLeft[ANY], j=n-1, k;
4452
4453         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4454         i = seed % k;  // pick one
4455         nrOfShuffles *= k;
4456         seed /= k;
4457         while(i >= j) i -= j--;
4458         j = n - 1 - j; i += j;
4459         put(board, pieceType, rank, j, ANY);
4460         put(board, pieceType, rank, i, ANY);
4461 }
4462
4463 void SetUpShuffle(Board board, int number)
4464 {
4465         int i, p, first=1;
4466
4467         GetPositionNumber(); nrOfShuffles = 1;
4468
4469         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4470         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4471         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4472
4473         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4474
4475         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4476             p = (int) board[0][i];
4477             if(p < (int) BlackPawn) piecesLeft[p] ++;
4478             board[0][i] = EmptySquare;
4479         }
4480
4481         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4482             // shuffles restricted to allow normal castling put KRR first
4483             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4484                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4485             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4486                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4487             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4488                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4489             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4490                 put(board, WhiteRook, 0, 0, ANY);
4491             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4492         }
4493
4494         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4495             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4496             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4497                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4498                 while(piecesLeft[p] >= 2) {
4499                     AddOnePiece(board, p, 0, LITE);
4500                     AddOnePiece(board, p, 0, DARK);
4501                 }
4502                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4503             }
4504
4505         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4506             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4507             // but we leave King and Rooks for last, to possibly obey FRC restriction
4508             if(p == (int)WhiteRook) continue;
4509             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4510             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4511         }
4512
4513         // now everything is placed, except perhaps King (Unicorn) and Rooks
4514
4515         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4516             // Last King gets castling rights
4517             while(piecesLeft[(int)WhiteUnicorn]) {
4518                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4519                 initialRights[2]  = initialRights[5]  = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4520             }
4521
4522             while(piecesLeft[(int)WhiteKing]) {
4523                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4524                 initialRights[2]  = initialRights[5]  = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4525             }
4526
4527
4528         } else {
4529             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4530             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4531         }
4532
4533         // Only Rooks can be left; simply place them all
4534         while(piecesLeft[(int)WhiteRook]) {
4535                 i = put(board, WhiteRook, 0, 0, ANY);
4536                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4537                         if(first) {
4538                                 first=0;
4539                                 initialRights[1]  = initialRights[4]  = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
4540                         }
4541                         initialRights[0]  = initialRights[3]  = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
4542                 }
4543         }
4544         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4545             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4546         }
4547
4548         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4549 }
4550
4551 int SetCharTable( char *table, const char * map )
4552 /* [HGM] moved here from winboard.c because of its general usefulness */
4553 /*       Basically a safe strcpy that uses the last character as King */
4554 {
4555     int result = FALSE; int NrPieces;
4556
4557     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4558                     && NrPieces >= 12 && !(NrPieces&1)) {
4559         int i; /* [HGM] Accept even length from 12 to 34 */
4560
4561         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4562         for( i=0; i<NrPieces/2-1; i++ ) {
4563             table[i] = map[i];
4564             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4565         }
4566         table[(int) WhiteKing]  = map[NrPieces/2-1];
4567         table[(int) BlackKing]  = map[NrPieces-1];
4568
4569         result = TRUE;
4570     }
4571
4572     return result;
4573 }
4574
4575 void Prelude(Board board)
4576 {       // [HGM] superchess: random selection of exo-pieces
4577         int i, j, k; ChessSquare p;
4578         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4579
4580         GetPositionNumber(); // use FRC position number
4581
4582         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4583             SetCharTable(pieceToChar, appData.pieceToCharTable);
4584             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4585                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4586         }
4587
4588         j = seed%4;                 seed /= 4;
4589         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4590         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4591         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4592         j = seed%3 + (seed%3 >= j); seed /= 3;
4593         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4594         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4595         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4596         j = seed%3;                 seed /= 3;
4597         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4598         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4599         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4600         j = seed%2 + (seed%2 >= j); seed /= 2;
4601         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4602         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4603         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4604         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4605         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4606         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4607         put(board, exoPieces[0],    0, 0, ANY);
4608         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4609 }
4610
4611 void
4612 InitPosition(redraw)
4613      int redraw;
4614 {
4615     ChessSquare (* pieces)[BOARD_FILES];
4616     int i, j, pawnRow, overrule,
4617     oldx = gameInfo.boardWidth,
4618     oldy = gameInfo.boardHeight,
4619     oldh = gameInfo.holdingsWidth,
4620     oldv = gameInfo.variant;
4621
4622     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4623
4624     /* [AS] Initialize pv info list [HGM] and game status */
4625     {
4626         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4627             pvInfoList[i].depth = 0;
4628             boards[i][EP_STATUS] = EP_NONE;
4629             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4630         }
4631
4632         initialRulePlies = 0; /* 50-move counter start */
4633
4634         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4635         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4636     }
4637
4638
4639     /* [HGM] logic here is completely changed. In stead of full positions */
4640     /* the initialized data only consist of the two backranks. The switch */
4641     /* selects which one we will use, which is than copied to the Board   */
4642     /* initialPosition, which for the rest is initialized by Pawns and    */
4643     /* empty squares. This initial position is then copied to boards[0],  */
4644     /* possibly after shuffling, so that it remains available.            */
4645
4646     gameInfo.holdingsWidth = 0; /* default board sizes */
4647     gameInfo.boardWidth    = 8;
4648     gameInfo.boardHeight   = 8;
4649     gameInfo.holdingsSize  = 0;
4650     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4651     for(i=0; i<BOARD_FILES-2; i++)
4652       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4653     initialPosition[EP_STATUS] = EP_NONE;
4654     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4655
4656     switch (gameInfo.variant) {
4657     case VariantFischeRandom:
4658       shuffleOpenings = TRUE;
4659     default:
4660       pieces = FIDEArray;
4661       break;
4662     case VariantShatranj:
4663       pieces = ShatranjArray;
4664       nrCastlingRights = 0;
4665       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4666       break;
4667     case VariantTwoKings:
4668       pieces = twoKingsArray;
4669       break;
4670     case VariantCapaRandom:
4671       shuffleOpenings = TRUE;
4672     case VariantCapablanca:
4673       pieces = CapablancaArray;
4674       gameInfo.boardWidth = 10;
4675       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4676       break;
4677     case VariantGothic:
4678       pieces = GothicArray;
4679       gameInfo.boardWidth = 10;
4680       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4681       break;
4682     case VariantJanus:
4683       pieces = JanusArray;
4684       gameInfo.boardWidth = 10;
4685       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4686       nrCastlingRights = 6;
4687         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4688         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4689         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4690         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4691         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4692         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4693       break;
4694     case VariantFalcon:
4695       pieces = FalconArray;
4696       gameInfo.boardWidth = 10;
4697       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4698       break;
4699     case VariantXiangqi:
4700       pieces = XiangqiArray;
4701       gameInfo.boardWidth  = 9;
4702       gameInfo.boardHeight = 10;
4703       nrCastlingRights = 0;
4704       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4705       break;
4706     case VariantShogi:
4707       pieces = ShogiArray;
4708       gameInfo.boardWidth  = 9;
4709       gameInfo.boardHeight = 9;
4710       gameInfo.holdingsSize = 7;
4711       nrCastlingRights = 0;
4712       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4713       break;
4714     case VariantCourier:
4715       pieces = CourierArray;
4716       gameInfo.boardWidth  = 12;
4717       nrCastlingRights = 0;
4718       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4719       break;
4720     case VariantKnightmate:
4721       pieces = KnightmateArray;
4722       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4723       break;
4724     case VariantFairy:
4725       pieces = fairyArray;
4726       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4727       break;
4728     case VariantGreat:
4729       pieces = GreatArray;
4730       gameInfo.boardWidth = 10;
4731       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4732       gameInfo.holdingsSize = 8;
4733       break;
4734     case VariantSuper:
4735       pieces = FIDEArray;
4736       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4737       gameInfo.holdingsSize = 8;
4738       startedFromSetupPosition = TRUE;
4739       break;
4740     case VariantCrazyhouse:
4741     case VariantBughouse:
4742       pieces = FIDEArray;
4743       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4744       gameInfo.holdingsSize = 5;
4745       break;
4746     case VariantWildCastle:
4747       pieces = FIDEArray;
4748       /* !!?shuffle with kings guaranteed to be on d or e file */
4749       shuffleOpenings = 1;
4750       break;
4751     case VariantNoCastle:
4752       pieces = FIDEArray;
4753       nrCastlingRights = 0;
4754       /* !!?unconstrained back-rank shuffle */
4755       shuffleOpenings = 1;
4756       break;
4757     }
4758
4759     overrule = 0;
4760     if(appData.NrFiles >= 0) {
4761         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4762         gameInfo.boardWidth = appData.NrFiles;
4763     }
4764     if(appData.NrRanks >= 0) {
4765         gameInfo.boardHeight = appData.NrRanks;
4766     }
4767     if(appData.holdingsSize >= 0) {
4768         i = appData.holdingsSize;
4769         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4770         gameInfo.holdingsSize = i;
4771     }
4772     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4773     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4774         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4775
4776     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4777     if(pawnRow < 1) pawnRow = 1;
4778
4779     /* User pieceToChar list overrules defaults */
4780     if(appData.pieceToCharTable != NULL)
4781         SetCharTable(pieceToChar, appData.pieceToCharTable);
4782
4783     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4784
4785         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4786             s = (ChessSquare) 0; /* account holding counts in guard band */
4787         for( i=0; i<BOARD_HEIGHT; i++ )
4788             initialPosition[i][j] = s;
4789
4790         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4791         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4792         initialPosition[pawnRow][j] = WhitePawn;
4793         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4794         if(gameInfo.variant == VariantXiangqi) {
4795             if(j&1) {
4796                 initialPosition[pawnRow][j] =
4797                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4798                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4799                    initialPosition[2][j] = WhiteCannon;
4800                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4801                 }
4802             }
4803         }
4804         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4805     }
4806     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4807
4808             j=BOARD_LEFT+1;
4809             initialPosition[1][j] = WhiteBishop;
4810             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4811             j=BOARD_RGHT-2;
4812             initialPosition[1][j] = WhiteRook;
4813             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4814     }
4815
4816     if( nrCastlingRights == -1) {
4817         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4818         /*       This sets default castling rights from none to normal corners   */
4819         /* Variants with other castling rights must set them themselves above    */
4820         nrCastlingRights = 6;
4821         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4822         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4823         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4824         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4825         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4826         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4827      }
4828
4829      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4830      if(gameInfo.variant == VariantGreat) { // promotion commoners
4831         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4832         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4833         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4834         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4835      }
4836   if (appData.debugMode) {
4837     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4838   }
4839     if(shuffleOpenings) {
4840         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4841         startedFromSetupPosition = TRUE;
4842     }
4843     if(startedFromPositionFile) {
4844       /* [HGM] loadPos: use PositionFile for every new game */
4845       CopyBoard(initialPosition, filePosition);
4846       for(i=0; i<nrCastlingRights; i++)
4847           initialRights[i] = filePosition[CASTLING][i];
4848       startedFromSetupPosition = TRUE;
4849     }
4850
4851     CopyBoard(boards[0], initialPosition);
4852     if(oldx != gameInfo.boardWidth ||
4853        oldy != gameInfo.boardHeight ||
4854        oldh != gameInfo.holdingsWidth
4855 #ifdef GOTHIC
4856        || oldv == VariantGothic ||        // For licensing popups
4857        gameInfo.variant == VariantGothic
4858 #endif
4859 #ifdef FALCON
4860        || oldv == VariantFalcon ||
4861        gameInfo.variant == VariantFalcon
4862 #endif
4863                                          )
4864       {
4865             InitDrawingSizes(-2 ,0);
4866       }
4867
4868     if (redraw)
4869       DrawPosition(TRUE, boards[currentMove]);
4870
4871 }
4872
4873 void
4874 SendBoard(cps, moveNum)
4875      ChessProgramState *cps;
4876      int moveNum;
4877 {
4878     char message[MSG_SIZ];
4879
4880     if (cps->useSetboard) {
4881       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4882       sprintf(message, "setboard %s\n", fen);
4883       SendToProgram(message, cps);
4884       free(fen);
4885
4886     } else {
4887       ChessSquare *bp;
4888       int i, j;
4889       /* Kludge to set black to move, avoiding the troublesome and now
4890        * deprecated "black" command.
4891        */
4892       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4893
4894       SendToProgram("edit\n", cps);
4895       SendToProgram("#\n", cps);
4896       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4897         bp = &boards[moveNum][i][BOARD_LEFT];
4898         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4899           if ((int) *bp < (int) BlackPawn) {
4900             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4901                     AAA + j, ONE + i);
4902             if(message[0] == '+' || message[0] == '~') {
4903                 sprintf(message, "%c%c%c+\n",
4904                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4905                         AAA + j, ONE + i);
4906             }
4907             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4908                 message[1] = BOARD_RGHT   - 1 - j + '1';
4909                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4910             }
4911             SendToProgram(message, cps);
4912           }
4913         }
4914       }
4915
4916       SendToProgram("c\n", cps);
4917       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4918         bp = &boards[moveNum][i][BOARD_LEFT];
4919         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4920           if (((int) *bp != (int) EmptySquare)
4921               && ((int) *bp >= (int) BlackPawn)) {
4922             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4923                     AAA + j, ONE + i);
4924             if(message[0] == '+' || message[0] == '~') {
4925                 sprintf(message, "%c%c%c+\n",
4926                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4927                         AAA + j, ONE + i);
4928             }
4929             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4930                 message[1] = BOARD_RGHT   - 1 - j + '1';
4931                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4932             }
4933             SendToProgram(message, cps);
4934           }
4935         }
4936       }
4937
4938       SendToProgram(".\n", cps);
4939     }
4940     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4941 }
4942
4943 int
4944 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4945 {
4946     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4947     /* [HGM] add Shogi promotions */
4948     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4949     ChessSquare piece;
4950     ChessMove moveType;
4951     Boolean premove;
4952
4953     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4954     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4955
4956     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4957       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4958         return FALSE;
4959
4960     piece = boards[currentMove][fromY][fromX];
4961     if(gameInfo.variant == VariantShogi) {
4962         promotionZoneSize = 3;
4963         highestPromotingPiece = (int)WhiteFerz;
4964     }
4965
4966     // next weed out all moves that do not touch the promotion zone at all
4967     if((int)piece >= BlackPawn) {
4968         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4969              return FALSE;
4970         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4971     } else {
4972         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4973            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4974     }
4975
4976     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4977
4978     // weed out mandatory Shogi promotions
4979     if(gameInfo.variant == VariantShogi) {
4980         if(piece >= BlackPawn) {
4981             if(toY == 0 && piece == BlackPawn ||
4982                toY == 0 && piece == BlackQueen ||
4983                toY <= 1 && piece == BlackKnight) {
4984                 *promoChoice = '+';
4985                 return FALSE;
4986             }
4987         } else {
4988             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4989                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4990                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4991                 *promoChoice = '+';
4992                 return FALSE;
4993             }
4994         }
4995     }
4996
4997     // weed out obviously illegal Pawn moves
4998     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4999         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5000         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5001         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5002         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5003         // note we are not allowed to test for valid (non-)capture, due to premove
5004     }
5005
5006     // we either have a choice what to promote to, or (in Shogi) whether to promote
5007     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5008         *promoChoice = PieceToChar(BlackFerz);  // no choice
5009         return FALSE;
5010     }
5011     if(appData.alwaysPromoteToQueen) { // predetermined
5012         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5013              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5014         else *promoChoice = PieceToChar(BlackQueen);
5015         return FALSE;
5016     }
5017
5018     // suppress promotion popup on illegal moves that are not premoves
5019     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5020               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5021     if(appData.testLegality && !premove) {
5022         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5023                         fromY, fromX, toY, toX, NULLCHAR);
5024         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5025            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5026             return FALSE;
5027     }
5028
5029     return TRUE;
5030 }
5031
5032 int
5033 InPalace(row, column)
5034      int row, column;
5035 {   /* [HGM] for Xiangqi */
5036     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5037          column < (BOARD_WIDTH + 4)/2 &&
5038          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5039     return FALSE;
5040 }
5041
5042 int
5043 PieceForSquare (x, y)
5044      int x;
5045      int y;
5046 {
5047   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5048      return -1;
5049   else
5050      return boards[currentMove][y][x];
5051 }
5052
5053 int
5054 OKToStartUserMove(x, y)
5055      int x, y;
5056 {
5057     ChessSquare from_piece;
5058     int white_piece;
5059
5060     if (matchMode) return FALSE;
5061     if (gameMode == EditPosition) return TRUE;
5062
5063     if (x >= 0 && y >= 0)
5064       from_piece = boards[currentMove][y][x];
5065     else
5066       from_piece = EmptySquare;
5067
5068     if (from_piece == EmptySquare) return FALSE;
5069
5070     white_piece = (int)from_piece >= (int)WhitePawn &&
5071       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5072
5073     switch (gameMode) {
5074       case PlayFromGameFile:
5075       case AnalyzeFile:
5076       case TwoMachinesPlay:
5077       case EndOfGame:
5078         return FALSE;
5079
5080       case IcsObserving:
5081       case IcsIdle:
5082         return FALSE;
5083
5084       case MachinePlaysWhite:
5085       case IcsPlayingBlack:
5086         if (appData.zippyPlay) return FALSE;
5087         if (white_piece) {
5088             DisplayMoveError(_("You are playing Black"));
5089             return FALSE;
5090         }
5091         break;
5092
5093       case MachinePlaysBlack:
5094       case IcsPlayingWhite:
5095         if (appData.zippyPlay) return FALSE;
5096         if (!white_piece) {
5097             DisplayMoveError(_("You are playing White"));
5098             return FALSE;
5099         }
5100         break;
5101
5102       case EditGame:
5103         if (!white_piece && WhiteOnMove(currentMove)) {
5104             DisplayMoveError(_("It is White's turn"));
5105             return FALSE;
5106         }
5107         if (white_piece && !WhiteOnMove(currentMove)) {
5108             DisplayMoveError(_("It is Black's turn"));
5109             return FALSE;
5110         }
5111         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5112             /* Editing correspondence game history */
5113             /* Could disallow this or prompt for confirmation */
5114             cmailOldMove = -1;
5115         }
5116         break;
5117
5118       case BeginningOfGame:
5119         if (appData.icsActive) return FALSE;
5120         if (!appData.noChessProgram) {
5121             if (!white_piece) {
5122                 DisplayMoveError(_("You are playing White"));
5123                 return FALSE;
5124             }
5125         }
5126         break;
5127
5128       case Training:
5129         if (!white_piece && WhiteOnMove(currentMove)) {
5130             DisplayMoveError(_("It is White's turn"));
5131             return FALSE;
5132         }
5133         if (white_piece && !WhiteOnMove(currentMove)) {
5134             DisplayMoveError(_("It is Black's turn"));
5135             return FALSE;
5136         }
5137         break;
5138
5139       default:
5140       case IcsExamining:
5141         break;
5142     }
5143     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5144         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5145         && gameMode != AnalyzeFile && gameMode != Training) {
5146         DisplayMoveError(_("Displayed position is not current"));
5147         return FALSE;
5148     }
5149     return TRUE;
5150 }
5151
5152 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5153 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5154 int lastLoadGameUseList = FALSE;
5155 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5156 ChessMove lastLoadGameStart = (ChessMove) 0;
5157
5158 ChessMove
5159 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5160      int fromX, fromY, toX, toY;
5161      int promoChar;
5162      Boolean captureOwn;
5163 {
5164     ChessMove moveType;
5165     ChessSquare pdown, pup;
5166
5167     /* Check if the user is playing in turn.  This is complicated because we
5168        let the user "pick up" a piece before it is his turn.  So the piece he
5169        tried to pick up may have been captured by the time he puts it down!
5170        Therefore we use the color the user is supposed to be playing in this
5171        test, not the color of the piece that is currently on the starting
5172        square---except in EditGame mode, where the user is playing both
5173        sides; fortunately there the capture race can't happen.  (It can
5174        now happen in IcsExamining mode, but that's just too bad.  The user
5175        will get a somewhat confusing message in that case.)
5176        */
5177
5178     switch (gameMode) {
5179       case PlayFromGameFile:
5180       case AnalyzeFile:
5181       case TwoMachinesPlay:
5182       case EndOfGame:
5183       case IcsObserving:
5184       case IcsIdle:
5185         /* We switched into a game mode where moves are not accepted,
5186            perhaps while the mouse button was down. */
5187         return ImpossibleMove;
5188
5189       case MachinePlaysWhite:
5190         /* User is moving for Black */
5191         if (WhiteOnMove(currentMove)) {
5192             DisplayMoveError(_("It is White's turn"));
5193             return ImpossibleMove;
5194         }
5195         break;
5196
5197       case MachinePlaysBlack:
5198         /* User is moving for White */
5199         if (!WhiteOnMove(currentMove)) {
5200             DisplayMoveError(_("It is Black's turn"));
5201             return ImpossibleMove;
5202         }
5203         break;
5204
5205       case EditGame:
5206       case IcsExamining:
5207       case BeginningOfGame:
5208       case AnalyzeMode:
5209       case Training:
5210         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5211             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5212             /* User is moving for Black */
5213             if (WhiteOnMove(currentMove)) {
5214                 DisplayMoveError(_("It is White's turn"));
5215                 return ImpossibleMove;
5216             }
5217         } else {
5218             /* User is moving for White */
5219             if (!WhiteOnMove(currentMove)) {
5220                 DisplayMoveError(_("It is Black's turn"));
5221                 return ImpossibleMove;
5222             }
5223         }
5224         break;
5225
5226       case IcsPlayingBlack:
5227         /* User is moving for Black */
5228         if (WhiteOnMove(currentMove)) {
5229             if (!appData.premove) {
5230                 DisplayMoveError(_("It is White's turn"));
5231             } else if (toX >= 0 && toY >= 0) {
5232                 premoveToX = toX;
5233                 premoveToY = toY;
5234                 premoveFromX = fromX;
5235                 premoveFromY = fromY;
5236                 premovePromoChar = promoChar;
5237                 gotPremove = 1;
5238                 if (appData.debugMode)
5239                     fprintf(debugFP, "Got premove: fromX %d,"
5240                             "fromY %d, toX %d, toY %d\n",
5241                             fromX, fromY, toX, toY);
5242             }
5243             return ImpossibleMove;
5244         }
5245         break;
5246
5247       case IcsPlayingWhite:
5248         /* User is moving for White */
5249         if (!WhiteOnMove(currentMove)) {
5250             if (!appData.premove) {
5251                 DisplayMoveError(_("It is Black's turn"));
5252             } else if (toX >= 0 && toY >= 0) {
5253                 premoveToX = toX;
5254                 premoveToY = toY;
5255                 premoveFromX = fromX;
5256                 premoveFromY = fromY;
5257                 premovePromoChar = promoChar;
5258                 gotPremove = 1;
5259                 if (appData.debugMode)
5260                     fprintf(debugFP, "Got premove: fromX %d,"
5261                             "fromY %d, toX %d, toY %d\n",
5262                             fromX, fromY, toX, toY);
5263             }
5264             return ImpossibleMove;
5265         }
5266         break;
5267
5268       default:
5269         break;
5270
5271       case EditPosition:
5272         /* EditPosition, empty square, or different color piece;
5273            click-click move is possible */
5274         if (toX == -2 || toY == -2) {
5275             boards[0][fromY][fromX] = EmptySquare;
5276             return AmbiguousMove;
5277         } else if (toX >= 0 && toY >= 0) {
5278             boards[0][toY][toX] = boards[0][fromY][fromX];
5279             boards[0][fromY][fromX] = EmptySquare;
5280             return AmbiguousMove;
5281         }
5282         return ImpossibleMove;
5283     }
5284
5285     if(toX < 0 || toY < 0) return ImpossibleMove;
5286     pdown = boards[currentMove][fromY][fromX];
5287     pup = boards[currentMove][toY][toX];
5288
5289     /* [HGM] If move started in holdings, it means a drop */
5290     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5291          if( pup != EmptySquare ) return ImpossibleMove;
5292          if(appData.testLegality) {
5293              /* it would be more logical if LegalityTest() also figured out
5294               * which drops are legal. For now we forbid pawns on back rank.
5295               * Shogi is on its own here...
5296               */
5297              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5298                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5299                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5300          }
5301          return WhiteDrop; /* Not needed to specify white or black yet */
5302     }
5303
5304     userOfferedDraw = FALSE;
5305
5306     /* [HGM] always test for legality, to get promotion info */
5307     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5308                                          fromY, fromX, toY, toX, promoChar);
5309     /* [HGM] but possibly ignore an IllegalMove result */
5310     if (appData.testLegality) {
5311         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5312             DisplayMoveError(_("Illegal move"));
5313             return ImpossibleMove;
5314         }
5315     }
5316
5317     return moveType;
5318     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5319        function is made into one that returns an OK move type if FinishMove
5320        should be called. This to give the calling driver routine the
5321        opportunity to finish the userMove input with a promotion popup,
5322        without bothering the user with this for invalid or illegal moves */
5323
5324 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5325 }
5326
5327 /* Common tail of UserMoveEvent and DropMenuEvent */
5328 int
5329 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5330      ChessMove moveType;
5331      int fromX, fromY, toX, toY;
5332      /*char*/int promoChar;
5333 {
5334   char *bookHit = 0;
5335
5336   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5337     {
5338       // [HGM] superchess: suppress promotions to non-available piece
5339       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5340       if(WhiteOnMove(currentMove))
5341         {
5342           if(!boards[currentMove][k][BOARD_WIDTH-2])
5343             return 0;
5344         }
5345       else
5346         {
5347           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5348             return 0;
5349         }
5350     }
5351   
5352   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5353      move type in caller when we know the move is a legal promotion */
5354   if(moveType == NormalMove && promoChar)
5355     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5356   
5357   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5358      move type in caller when we know the move is a legal promotion */
5359   if(moveType == NormalMove && promoChar)
5360     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5361   
5362   /* [HGM] convert drag-and-drop piece drops to standard form */
5363   if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5364     {
5365       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5366       if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5367                                     moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5368       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5369       if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5370       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5371       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5372       fromY = DROP_RANK;
5373     }
5374   
5375   /* [HGM] <popupFix> The following if has been moved here from
5376      UserMoveEvent(). Because it seemed to belong here (why not allow
5377      piece drops in training games?), and because it can only be
5378      performed after it is known to what we promote. */
5379   if (gameMode == Training) 
5380     {
5381       /* compare the move played on the board to the next move in the
5382        * game. If they match, display the move and the opponent's response.
5383        * If they don't match, display an error message.
5384        */
5385       int saveAnimate;
5386       Board testBoard;
5387       CopyBoard(testBoard, boards[currentMove]);
5388       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5389
5390       if (CompareBoards(testBoard, boards[currentMove+1]))
5391         {
5392           ForwardInner(currentMove+1);
5393
5394           /* Autoplay the opponent's response.
5395            * if appData.animate was TRUE when Training mode was entered,
5396            * the response will be animated.
5397            */
5398           saveAnimate = appData.animate;
5399           appData.animate = animateTraining;
5400           ForwardInner(currentMove+1);
5401           appData.animate = saveAnimate;
5402
5403           /* check for the end of the game */
5404           if (currentMove >= forwardMostMove)
5405             {
5406               gameMode = PlayFromGameFile;
5407               ModeHighlight();
5408               SetTrainingModeOff();
5409               DisplayInformation(_("End of game"));
5410             }
5411         }
5412       else
5413         {
5414           DisplayError(_("Incorrect move"), 0);
5415         }
5416       return 1;
5417     }
5418
5419   /* Ok, now we know that the move is good, so we can kill
5420      the previous line in Analysis Mode */
5421   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5422                                 && currentMove < forwardMostMove) {
5423     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5424   }
5425
5426   /* If we need the chess program but it's dead, restart it */
5427   ResurrectChessProgram();
5428
5429   /* A user move restarts a paused game*/
5430   if (pausing)
5431     PauseEvent();
5432
5433   thinkOutput[0] = NULLCHAR;
5434
5435   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5436
5437   if (gameMode == BeginningOfGame)
5438     {
5439       if (appData.noChessProgram)
5440         {
5441           gameMode = EditGame;
5442           SetGameInfo();
5443         }
5444       else
5445         {
5446           char buf[MSG_SIZ];
5447           gameMode = MachinePlaysBlack;
5448           StartClocks();
5449           SetGameInfo();
5450           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5451           DisplayTitle(buf);
5452           if (first.sendName)
5453             {
5454               sprintf(buf, "name %s\n", gameInfo.white);
5455               SendToProgram(buf, &first);
5456             }
5457           StartClocks();
5458         }
5459       ModeHighlight();
5460     }
5461     ModeHighlight();
5462   }
5463
5464   /* Relay move to ICS or chess engine */
5465   if (appData.icsActive)
5466     {
5467       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5468           gameMode == IcsExamining)
5469         {
5470           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5471           ics_user_moved = 1;
5472         }
5473     }
5474   else
5475     {
5476       if (first.sendTime && (gameMode == BeginningOfGame ||
5477                              gameMode == MachinePlaysWhite ||
5478                              gameMode == MachinePlaysBlack))
5479         {
5480           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5481         }
5482       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5483         {
5484           // [HGM] book: if program might be playing, let it use book
5485           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5486           first.maybeThinking = TRUE;
5487         }
5488       else
5489         SendMoveToProgram(forwardMostMove-1, &first);
5490       if (currentMove == cmailOldMove + 1)
5491         {
5492           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5493         }
5494     }
5495
5496   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5497
5498   switch (gameMode) 
5499     {
5500     case EditGame:
5501       switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5502       case MT_NONE:
5503       case MT_CHECK:
5504         break;
5505
5506     case MachinePlaysBlack:
5507     case MachinePlaysWhite:
5508       /* disable certain menu options while machine is thinking */
5509       SetMachineThinkingEnables();
5510       break;
5511
5512     default:
5513       break;
5514     }
5515
5516   if(bookHit)
5517     { // [HGM] book: simulate book reply
5518       static char bookMove[MSG_SIZ]; // a bit generous?
5519
5520       programStats.nodes = programStats.depth = programStats.time =
5521         programStats.score = programStats.got_only_move = 0;
5522       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5523
5524       strcpy(bookMove, "move ");
5525       strcat(bookMove, bookHit);
5526       HandleMachineMove(bookMove, &first);
5527     }
5528
5529   return 1;
5530 }
5531
5532 void
5533 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5534      int fromX, fromY, toX, toY;
5535      int promoChar;
5536 {
5537     /* [HGM] This routine was added to allow calling of its two logical
5538        parts from other modules in the old way. Before, UserMoveEvent()
5539        automatically called FinishMove() if the move was OK, and returned
5540        otherwise. I separated the two, in order to make it possible to
5541        slip a promotion popup in between. But that it always needs two
5542        calls, to the first part, (now called UserMoveTest() ), and to
5543        FinishMove if the first part succeeded. Calls that do not need
5544        to do anything in between, can call this routine the old way.
5545     */
5546   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5547   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5548   if(moveType == AmbiguousMove)
5549     DrawPosition(FALSE, boards[currentMove]);
5550   else if(moveType != ImpossibleMove && moveType != Comment)
5551     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5552 }
5553
5554 void LeftClick(ClickType clickType, int xPix, int yPix)
5555 {
5556     int x, y;
5557     Boolean saveAnimate;
5558     static int second = 0, promotionChoice = 0;
5559     char promoChoice = NULLCHAR;
5560
5561     if (clickType == Press) ErrorPopDown();
5562
5563     x = EventToSquare(xPix, BOARD_WIDTH);
5564     y = EventToSquare(yPix, BOARD_HEIGHT);
5565     if (!flipView && y >= 0) {
5566         y = BOARD_HEIGHT - 1 - y;
5567     }
5568     if (flipView && x >= 0) {
5569         x = BOARD_WIDTH - 1 - x;
5570     }
5571
5572     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5573         if(clickType == Release) return; // ignore upclick of click-click destination
5574         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5575         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5576         if(gameInfo.holdingsWidth && 
5577                 (WhiteOnMove(currentMove) 
5578                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5579                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5580             // click in right holdings, for determining promotion piece
5581             ChessSquare p = boards[currentMove][y][x];
5582             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5583             if(p != EmptySquare) {
5584                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5585                 fromX = fromY = -1;
5586                 return;
5587             }
5588         }
5589         DrawPosition(FALSE, boards[currentMove]);
5590         return;
5591     }
5592
5593     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5594     if(clickType == Press
5595             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5596               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5597               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5598         return;
5599
5600     if (fromX == -1) {
5601         if (clickType == Press) {
5602             /* First square */
5603             if (OKToStartUserMove(x, y)) {
5604                 fromX = x;
5605                 fromY = y;
5606                 second = 0;
5607                 DragPieceBegin(xPix, yPix);
5608                 if (appData.highlightDragging) {
5609                     SetHighlights(x, y, -1, -1);
5610                 }
5611             }
5612         }
5613         return;
5614     }
5615
5616     /* fromX != -1 */
5617     if (clickType == Press && gameMode != EditPosition) {
5618         ChessSquare fromP;
5619         ChessSquare toP;
5620         int frc;
5621
5622         // ignore off-board to clicks
5623         if(y < 0 || x < 0) return;
5624
5625         /* Check if clicking again on the same color piece */
5626         fromP = boards[currentMove][fromY][fromX];
5627         toP = boards[currentMove][y][x];
5628         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5629         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5630              WhitePawn <= toP && toP <= WhiteKing &&
5631              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5632              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5633             (BlackPawn <= fromP && fromP <= BlackKing && 
5634              BlackPawn <= toP && toP <= BlackKing &&
5635              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5636              !(fromP == BlackKing && toP == BlackRook && frc))) {
5637             /* Clicked again on same color piece -- changed his mind */
5638             second = (x == fromX && y == fromY);
5639             if (appData.highlightDragging) {
5640                 SetHighlights(x, y, -1, -1);
5641             } else {
5642                 ClearHighlights();
5643             }
5644             if (OKToStartUserMove(x, y)) {
5645                 fromX = x;
5646                 fromY = y;
5647                 DragPieceBegin(xPix, yPix);
5648             }
5649             return;
5650         }
5651         // ignore clicks on holdings
5652         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5653     }
5654
5655     if (clickType == Release && x == fromX && y == fromY) {
5656         DragPieceEnd(xPix, yPix);
5657         if (appData.animateDragging) {
5658             /* Undo animation damage if any */
5659             DrawPosition(FALSE, NULL);
5660         }
5661         if (second) {
5662             /* Second up/down in same square; just abort move */
5663             second = 0;
5664             fromX = fromY = -1;
5665             ClearHighlights();
5666             gotPremove = 0;
5667             ClearPremoveHighlights();
5668         } else {
5669             /* First upclick in same square; start click-click mode */
5670             SetHighlights(x, y, -1, -1);
5671         }
5672         return;
5673     }
5674
5675     /* we now have a different from- and (possibly off-board) to-square */
5676     /* Completed move */
5677     toX = x;
5678     toY = y;
5679     saveAnimate = appData.animate;
5680     if (clickType == Press) {
5681         /* Finish clickclick move */
5682         if (appData.animate || appData.highlightLastMove) {
5683             SetHighlights(fromX, fromY, toX, toY);
5684         } else {
5685             ClearHighlights();
5686         }
5687     } else {
5688         /* Finish drag move */
5689         if (appData.highlightLastMove) {
5690             SetHighlights(fromX, fromY, toX, toY);
5691         } else {
5692             ClearHighlights();
5693         }
5694         DragPieceEnd(xPix, yPix);
5695         /* Don't animate move and drag both */
5696         appData.animate = FALSE;
5697     }
5698
5699     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5700     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5701         ClearHighlights();
5702         fromX = fromY = -1;
5703         DrawPosition(TRUE, NULL);
5704         return;
5705     }
5706
5707     // off-board moves should not be highlighted
5708     if(x < 0 || x < 0) ClearHighlights();
5709
5710     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5711         SetHighlights(fromX, fromY, toX, toY);
5712         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5713             // [HGM] super: promotion to captured piece selected from holdings
5714             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5715             promotionChoice = TRUE;
5716             // kludge follows to temporarily execute move on display, without promoting yet
5717             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5718             boards[currentMove][toY][toX] = p;
5719             DrawPosition(FALSE, boards[currentMove]);
5720             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5721             boards[currentMove][toY][toX] = q;
5722             DisplayMessage("Click in holdings to choose piece", "");
5723             return;
5724         }
5725         PromotionPopUp();
5726     } else {
5727         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5728         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5729         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5730         fromX = fromY = -1;
5731     }
5732     appData.animate = saveAnimate;
5733     if (appData.animate || appData.animateDragging) {
5734         /* Undo animation damage if needed */
5735         DrawPosition(FALSE, NULL);
5736     }
5737 }
5738
5739 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5740 {
5741 //    char * hint = lastHint;
5742     FrontEndProgramStats stats;
5743
5744     stats.which = cps == &first ? 0 : 1;
5745     stats.depth = cpstats->depth;
5746     stats.nodes = cpstats->nodes;
5747     stats.score = cpstats->score;
5748     stats.time = cpstats->time;
5749     stats.pv = cpstats->movelist;
5750     stats.hint = lastHint;
5751     stats.an_move_index = 0;
5752     stats.an_move_count = 0;
5753
5754     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5755         stats.hint = cpstats->move_name;
5756         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5757         stats.an_move_count = cpstats->nr_moves;
5758     }
5759
5760     SetProgramStats( &stats );
5761 }
5762
5763 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5764 {   // [HGM] book: this routine intercepts moves to simulate book replies
5765     char *bookHit = NULL;
5766
5767     //first determine if the incoming move brings opponent into his book
5768     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5769         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5770     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5771     if(bookHit != NULL && !cps->bookSuspend) {
5772         // make sure opponent is not going to reply after receiving move to book position
5773         SendToProgram("force\n", cps);
5774         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5775     }
5776     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5777     // now arrange restart after book miss
5778     if(bookHit) {
5779         // after a book hit we never send 'go', and the code after the call to this routine
5780         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5781         char buf[MSG_SIZ];
5782         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5783         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5784         SendToProgram(buf, cps);
5785         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5786     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5787         SendToProgram("go\n", cps);
5788         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5789     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5790         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5791             SendToProgram("go\n", cps);
5792         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5793     }
5794     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5795 }
5796
5797 char *savedMessage;
5798 ChessProgramState *savedState;
5799 void DeferredBookMove(void)
5800 {
5801         if(savedState->lastPing != savedState->lastPong)
5802                     ScheduleDelayedEvent(DeferredBookMove, 10);
5803         else
5804         HandleMachineMove(savedMessage, savedState);
5805 }
5806
5807 void
5808 HandleMachineMove(message, cps)
5809      char *message;
5810      ChessProgramState *cps;
5811 {
5812     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5813     char realname[MSG_SIZ];
5814     int fromX, fromY, toX, toY;
5815     ChessMove moveType;
5816     char promoChar;
5817     char *p;
5818     int machineWhite;
5819     char *bookHit;
5820
5821 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5822     /*
5823      * Kludge to ignore BEL characters
5824      */
5825     while (*message == '\007') message++;
5826
5827     /*
5828      * [HGM] engine debug message: ignore lines starting with '#' character
5829      */
5830     if(cps->debug && *message == '#') return;
5831
5832     /*
5833      * Look for book output
5834      */
5835     if (cps == &first && bookRequested) {
5836         if (message[0] == '\t' || message[0] == ' ') {
5837             /* Part of the book output is here; append it */
5838             strcat(bookOutput, message);
5839             strcat(bookOutput, "  \n");
5840             return;
5841         } else if (bookOutput[0] != NULLCHAR) {
5842             /* All of book output has arrived; display it */
5843             char *p = bookOutput;
5844             while (*p != NULLCHAR) {
5845                 if (*p == '\t') *p = ' ';
5846                 p++;
5847             }
5848             DisplayInformation(bookOutput);
5849             bookRequested = FALSE;
5850             /* Fall through to parse the current output */
5851         }
5852     }
5853
5854     /*
5855      * Look for machine move.
5856      */
5857     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5858         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5859     {
5860         /* This method is only useful on engines that support ping */
5861         if (cps->lastPing != cps->lastPong) {
5862           if (gameMode == BeginningOfGame) {
5863             /* Extra move from before last new; ignore */
5864             if (appData.debugMode) {
5865                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5866             }
5867           } else {
5868             if (appData.debugMode) {
5869                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5870                         cps->which, gameMode);
5871             }
5872
5873             SendToProgram("undo\n", cps);
5874           }
5875           return;
5876         }
5877
5878         switch (gameMode) {
5879           case BeginningOfGame:
5880             /* Extra move from before last reset; ignore */
5881             if (appData.debugMode) {
5882                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5883             }
5884             return;
5885
5886           case EndOfGame:
5887           case IcsIdle:
5888           default:
5889             /* Extra move after we tried to stop.  The mode test is
5890                not a reliable way of detecting this problem, but it's
5891                the best we can do on engines that don't support ping.
5892             */
5893             if (appData.debugMode) {
5894                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5895                         cps->which, gameMode);
5896             }
5897             SendToProgram("undo\n", cps);
5898             return;
5899
5900           case MachinePlaysWhite:
5901           case IcsPlayingWhite:
5902             machineWhite = TRUE;
5903             break;
5904
5905           case MachinePlaysBlack:
5906           case IcsPlayingBlack:
5907             machineWhite = FALSE;
5908             break;
5909
5910           case TwoMachinesPlay:
5911             machineWhite = (cps->twoMachinesColor[0] == 'w');
5912             break;
5913         }
5914         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5915             if (appData.debugMode) {
5916                 fprintf(debugFP,
5917                         "Ignoring move out of turn by %s, gameMode %d"
5918                         ", forwardMost %d\n",
5919                         cps->which, gameMode, forwardMostMove);
5920             }
5921             return;
5922         }
5923
5924     if (appData.debugMode) { int f = forwardMostMove;
5925         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5926                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5927                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5928     }
5929         if(cps->alphaRank) AlphaRank(machineMove, 4);
5930         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5931                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5932             /* Machine move could not be parsed; ignore it. */
5933             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5934                     machineMove, cps->which);
5935             DisplayError(buf1, 0);
5936             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5937                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5938             if (gameMode == TwoMachinesPlay) {
5939               GameEnds(machineWhite ? BlackWins : WhiteWins,
5940                        buf1, GE_XBOARD);
5941             }
5942             return;
5943         }
5944
5945         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5946         /* So we have to redo legality test with true e.p. status here,  */
5947         /* to make sure an illegal e.p. capture does not slip through,   */
5948         /* to cause a forfeit on a justified illegal-move complaint      */
5949         /* of the opponent.                                              */
5950         if( gameMode==TwoMachinesPlay && appData.testLegality
5951             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5952                                                               ) {
5953            ChessMove moveType;
5954            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5955                              fromY, fromX, toY, toX, promoChar);
5956             if (appData.debugMode) {
5957                 int i;
5958                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5959                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5960                 fprintf(debugFP, "castling rights\n");
5961             }
5962             if(moveType == IllegalMove) {
5963                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5964                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5965                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5966                            buf1, GE_XBOARD);
5967                 return;
5968            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5969            /* [HGM] Kludge to handle engines that send FRC-style castling
5970               when they shouldn't (like TSCP-Gothic) */
5971            switch(moveType) {
5972              case WhiteASideCastleFR:
5973              case BlackASideCastleFR:
5974                toX+=2;
5975                currentMoveString[2]++;
5976                break;
5977              case WhiteHSideCastleFR:
5978              case BlackHSideCastleFR:
5979                toX--;
5980                currentMoveString[2]--;
5981                break;
5982              default: ; // nothing to do, but suppresses warning of pedantic compilers
5983            }
5984         }
5985         hintRequested = FALSE;
5986         lastHint[0] = NULLCHAR;
5987         bookRequested = FALSE;
5988         /* Program may be pondering now */
5989         cps->maybeThinking = TRUE;
5990         if (cps->sendTime == 2) cps->sendTime = 1;
5991         if (cps->offeredDraw) cps->offeredDraw--;
5992
5993 #if ZIPPY
5994         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5995             first.initDone) {
5996           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5997           ics_user_moved = 1;
5998           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5999                 char buf[3*MSG_SIZ];
6000
6001                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6002                         programStats.score / 100.,
6003                         programStats.depth,
6004                         programStats.time / 100.,
6005                         (unsigned int)programStats.nodes,
6006                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6007                         programStats.movelist);
6008                 SendToICS(buf);
6009 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6010           }
6011         }
6012 #endif
6013         /* currentMoveString is set as a side-effect of ParseOneMove */
6014         strcpy(machineMove, currentMoveString);
6015         strcat(machineMove, "\n");
6016         strcpy(moveList[forwardMostMove], machineMove);
6017
6018         /* [AS] Save move info and clear stats for next move */
6019         pvInfoList[ forwardMostMove ].score = programStats.score;
6020         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6021         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6022         ClearProgramStats();
6023         thinkOutput[0] = NULLCHAR;
6024         hiddenThinkOutputState = 0;
6025
6026         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6027
6028         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6029         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6030             int count = 0;
6031
6032             while( count < adjudicateLossPlies ) {
6033                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6034
6035                 if( count & 1 ) {
6036                     score = -score; /* Flip score for winning side */
6037                 }
6038
6039                 if( score > adjudicateLossThreshold ) {
6040                     break;
6041                 }
6042
6043                 count++;
6044             }
6045
6046             if( count >= adjudicateLossPlies ) {
6047                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6048
6049                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6050                     "Xboard adjudication",
6051                     GE_XBOARD );
6052
6053                 return;
6054             }
6055         }
6056
6057         if( gameMode == TwoMachinesPlay ) {
6058           // [HGM] some adjudications useful with buggy engines
6059             int k, count = 0; static int bare = 1;
6060           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6061
6062
6063             if( appData.testLegality )
6064             {   /* [HGM] Some more adjudications for obstinate engines */
6065                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6066                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6067                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6068                 static int moveCount = 6;
6069                 ChessMove result;
6070                 char *reason = NULL;
6071
6072                 /* Count what is on board. */
6073                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6074                 {   ChessSquare p = boards[forwardMostMove][i][j];
6075                     int m=i;
6076
6077                     switch((int) p)
6078                     {   /* count B,N,R and other of each side */
6079                         case WhiteKing:
6080                         case BlackKing:
6081                              NrK++; break; // [HGM] atomic: count Kings
6082                         case WhiteKnight:
6083                              NrWN++; break;
6084                         case WhiteBishop:
6085                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6086                              bishopsColor |= 1 << ((i^j)&1);
6087                              NrWB++; break;
6088                         case BlackKnight:
6089                              NrBN++; break;
6090                         case BlackBishop:
6091                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6092                              bishopsColor |= 1 << ((i^j)&1);
6093                              NrBB++; break;
6094                         case WhiteRook:
6095                              NrWR++; break;
6096                         case BlackRook:
6097                              NrBR++; break;
6098                         case WhiteQueen:
6099                              NrWQ++; break;
6100                         case BlackQueen:
6101                              NrBQ++; break;
6102                         case EmptySquare:
6103                              break;
6104                         case BlackPawn:
6105                              m = 7-i;
6106                         case WhitePawn:
6107                              PawnAdvance += m; NrPawns++;
6108                     }
6109                     NrPieces += (p != EmptySquare);
6110                     NrW += ((int)p < (int)BlackPawn);
6111                     if(gameInfo.variant == VariantXiangqi &&
6112                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6113                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6114                         NrW -= ((int)p < (int)BlackPawn);
6115                     }
6116                 }
6117
6118                 /* Some material-based adjudications that have to be made before stalemate test */
6119                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6120                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6121                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6122                      if(appData.checkMates) {
6123                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6124                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6125                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6126                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6127                          return;
6128                      }
6129                 }
6130
6131                 /* Bare King in Shatranj (loses) or Losers (wins) */
6132                 if( NrW == 1 || NrPieces - NrW == 1) {
6133                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6134                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6135                      if(appData.checkMates) {
6136                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6137                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6138                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6139                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6140                          return;
6141                      }
6142                   } else
6143                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6144                   {    /* bare King */
6145                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6146                         if(appData.checkMates) {
6147                             /* but only adjudicate if adjudication enabled */
6148                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6149                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6150                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6151                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6152                             return;
6153                         }
6154                   }
6155                 } else bare = 1;
6156
6157
6158             // don't wait for engine to announce game end if we can judge ourselves
6159             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6160               case MT_CHECK:
6161                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6162                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6163                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6164                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6165                             checkCnt++;
6166                         if(checkCnt >= 2) {
6167                             reason = "Xboard adjudication: 3rd check";
6168                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6169                             break;
6170                         }
6171                     }
6172                 }
6173               case MT_NONE:
6174               default:
6175                 break;
6176               case MT_STALEMATE:
6177               case MT_STAINMATE:
6178                 reason = "Xboard adjudication: Stalemate";
6179                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6180                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6181                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6182                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6183                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6184                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6185                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6186                                                                         EP_CHECKMATE : EP_WINS);
6187                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6188                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6189                 }
6190                 break;
6191               case MT_CHECKMATE:
6192                 reason = "Xboard adjudication: Checkmate";
6193                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6194                 break;
6195             }
6196
6197                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6198                     case EP_STALEMATE:
6199                         result = GameIsDrawn; break;
6200                     case EP_CHECKMATE:
6201                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6202                     case EP_WINS:
6203                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6204                     default:
6205                         result = (ChessMove) 0;
6206                 }
6207                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6208                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6209                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6210                     GameEnds( result, reason, GE_XBOARD );
6211                     return;
6212                 }
6213
6214                 /* Next absolutely insufficient mating material. */
6215                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6216                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6217                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6218                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6219                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6220
6221                      /* always flag draws, for judging claims */
6222                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6223
6224                      if(appData.materialDraws) {
6225                          /* but only adjudicate them if adjudication enabled */
6226                          SendToProgram("force\n", cps->other); // suppress reply
6227                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6228                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6229                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6230                          return;
6231                      }
6232                 }
6233
6234                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6235                 if(NrPieces == 4 &&
6236                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6237                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6238                    || NrWN==2 || NrBN==2     /* KNNK */
6239                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6240                   ) ) {
6241                      if(--moveCount < 0 && appData.trivialDraws)
6242                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6243                           SendToProgram("force\n", cps->other); // suppress reply
6244                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6245                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6246                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6247                           return;
6248                      }
6249                 } else moveCount = 6;
6250             }
6251           }
6252           
6253           if (appData.debugMode) { int i;
6254             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6255                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6256                     appData.drawRepeats);
6257             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6258               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6259             
6260           }
6261
6262                 /* Check for rep-draws */
6263                 count = 0;
6264                 for(k = forwardMostMove-2;
6265                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6266                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6267                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6268                     k-=2)
6269                 {   int rights=0;
6270                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6271                         /* compare castling rights */
6272                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6273                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6274                                 rights++; /* King lost rights, while rook still had them */
6275                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6276                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6277                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6278                                    rights++; /* but at least one rook lost them */
6279                         }
6280                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6281                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6282                                 rights++; 
6283                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6284                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6285                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6286                                    rights++;
6287                         }
6288                         if( rights == 0 && ++count > appData.drawRepeats-2
6289                             && appData.drawRepeats > 1) {
6290                              /* adjudicate after user-specified nr of repeats */
6291                              SendToProgram("force\n", cps->other); // suppress reply
6292                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6293                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6294                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6295                                 // [HGM] xiangqi: check for forbidden perpetuals
6296                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6297                                 for(m=forwardMostMove; m>k; m-=2) {
6298                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6299                                         ourPerpetual = 0; // the current mover did not always check
6300                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6301                                         hisPerpetual = 0; // the opponent did not always check
6302                                 }
6303                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6304                                                                         ourPerpetual, hisPerpetual);
6305                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6306                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6307                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6308                                     return;
6309                                 }
6310                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6311                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6312                                 // Now check for perpetual chases
6313                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6314                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6315                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6316                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6317                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6318                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6319                                         return;
6320                                     }
6321                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6322                                         break; // Abort repetition-checking loop.
6323                                 }
6324                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6325                              }
6326                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6327                              return;
6328                         }
6329                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6330                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6331                     }
6332                 }
6333
6334                 /* Now we test for 50-move draws. Determine ply count */
6335                 count = forwardMostMove;
6336                 /* look for last irreversble move */
6337                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6338                     count--;
6339                 /* if we hit starting position, add initial plies */
6340                 if( count == backwardMostMove )
6341                     count -= initialRulePlies;
6342                 count = forwardMostMove - count;
6343                 if( count >= 100)
6344                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6345                          /* this is used to judge if draw claims are legal */
6346                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6347                          SendToProgram("force\n", cps->other); // suppress reply
6348                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6349                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6350                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6351                          return;
6352                 }
6353
6354                 /* if draw offer is pending, treat it as a draw claim
6355                  * when draw condition present, to allow engines a way to
6356                  * claim draws before making their move to avoid a race
6357                  * condition occurring after their move
6358                  */
6359                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6360                          char *p = NULL;
6361                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6362                              p = "Draw claim: 50-move rule";
6363                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6364                              p = "Draw claim: 3-fold repetition";
6365                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6366                              p = "Draw claim: insufficient mating material";
6367                          if( p != NULL ) {
6368                              SendToProgram("force\n", cps->other); // suppress reply
6369                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6370                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6371                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6372                              return;
6373                          }
6374                 }
6375
6376
6377                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6378                     SendToProgram("force\n", cps->other); // suppress reply
6379                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6380                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6381
6382                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6383
6384                     return;
6385                 }
6386         }
6387
6388         bookHit = NULL;
6389         if (gameMode == TwoMachinesPlay) {
6390             /* [HGM] relaying draw offers moved to after reception of move */
6391             /* and interpreting offer as claim if it brings draw condition */
6392             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6393                 SendToProgram("draw\n", cps->other);
6394             }
6395             if (cps->other->sendTime) {
6396                 SendTimeRemaining(cps->other,
6397                                   cps->other->twoMachinesColor[0] == 'w');
6398             }
6399             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6400             if (firstMove && !bookHit) {
6401                 firstMove = FALSE;
6402                 if (cps->other->useColors) {
6403                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6404                 }
6405                 SendToProgram("go\n", cps->other);
6406             }
6407             cps->other->maybeThinking = TRUE;
6408         }
6409
6410         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6411
6412         if (!pausing && appData.ringBellAfterMoves) {
6413             RingBell();
6414         }
6415
6416         /*
6417          * Reenable menu items that were disabled while
6418          * machine was thinking
6419          */
6420         if (gameMode != TwoMachinesPlay)
6421             SetUserThinkingEnables();
6422
6423         // [HGM] book: after book hit opponent has received move and is now in force mode
6424         // force the book reply into it, and then fake that it outputted this move by jumping
6425         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6426         if(bookHit) {
6427                 static char bookMove[MSG_SIZ]; // a bit generous?
6428
6429                 strcpy(bookMove, "move ");
6430                 strcat(bookMove, bookHit);
6431                 message = bookMove;
6432                 cps = cps->other;
6433                 programStats.nodes = programStats.depth = programStats.time =
6434                 programStats.score = programStats.got_only_move = 0;
6435                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6436
6437                 if(cps->lastPing != cps->lastPong) {
6438                     savedMessage = message; // args for deferred call
6439                     savedState = cps;
6440                     ScheduleDelayedEvent(DeferredBookMove, 10);
6441                     return;
6442                 }
6443                 goto FakeBookMove;
6444         }
6445
6446         return;
6447     }
6448
6449     /* Set special modes for chess engines.  Later something general
6450      *  could be added here; for now there is just one kludge feature,
6451      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6452      *  when "xboard" is given as an interactive command.
6453      */
6454     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6455         cps->useSigint = FALSE;
6456         cps->useSigterm = FALSE;
6457     }
6458     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6459       ParseFeatures(message+8, cps);
6460       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6461     }
6462
6463     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6464      * want this, I was asked to put it in, and obliged.
6465      */
6466     if (!strncmp(message, "setboard ", 9)) {
6467         Board initial_position;
6468
6469         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6470
6471         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6472             DisplayError(_("Bad FEN received from engine"), 0);
6473             return ;
6474         } else {
6475            Reset(TRUE, FALSE);
6476            CopyBoard(boards[0], initial_position);
6477            initialRulePlies = FENrulePlies;
6478            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6479            else gameMode = MachinePlaysBlack;
6480            DrawPosition(FALSE, boards[currentMove]);
6481         }
6482         return;
6483     }
6484
6485     /*
6486      * Look for communication commands
6487      */
6488     if (!strncmp(message, "telluser ", 9)) {
6489         DisplayNote(message + 9);
6490         return;
6491     }
6492     if (!strncmp(message, "tellusererror ", 14)) {
6493         DisplayError(message + 14, 0);
6494         return;
6495     }
6496     if (!strncmp(message, "tellopponent ", 13)) {
6497       if (appData.icsActive) {
6498         if (loggedOn) {
6499           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6500           SendToICS(buf1);
6501         }
6502       } else {
6503         DisplayNote(message + 13);
6504       }
6505       return;
6506     }
6507     if (!strncmp(message, "tellothers ", 11)) {
6508       if (appData.icsActive) {
6509         if (loggedOn) {
6510           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6511           SendToICS(buf1);
6512         }
6513       }
6514       return;
6515     }
6516     if (!strncmp(message, "tellall ", 8)) {
6517       if (appData.icsActive) {
6518         if (loggedOn) {
6519           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6520           SendToICS(buf1);
6521         }
6522       } else {
6523         DisplayNote(message + 8);
6524       }
6525       return;
6526     }
6527     if (strncmp(message, "warning", 7) == 0) {
6528         /* Undocumented feature, use tellusererror in new code */
6529         DisplayError(message, 0);
6530         return;
6531     }
6532     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6533         strcpy(realname, cps->tidy);
6534         strcat(realname, " query");
6535         AskQuestion(realname, buf2, buf1, cps->pr);
6536         return;
6537     }
6538     /* Commands from the engine directly to ICS.  We don't allow these to be
6539      *  sent until we are logged on. Crafty kibitzes have been known to
6540      *  interfere with the login process.
6541      */
6542     if (loggedOn) {
6543         if (!strncmp(message, "tellics ", 8)) {
6544             SendToICS(message + 8);
6545             SendToICS("\n");
6546             return;
6547         }
6548         if (!strncmp(message, "tellicsnoalias ", 15)) {
6549             SendToICS(ics_prefix);
6550             SendToICS(message + 15);
6551             SendToICS("\n");
6552             return;
6553         }
6554         /* The following are for backward compatibility only */
6555         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6556             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6557             SendToICS(ics_prefix);
6558             SendToICS(message);
6559             SendToICS("\n");
6560             return;
6561         }
6562     }
6563     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6564         return;
6565     }
6566     /*
6567      * If the move is illegal, cancel it and redraw the board.
6568      * Also deal with other error cases.  Matching is rather loose
6569      * here to accommodate engines written before the spec.
6570      */
6571     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6572         strncmp(message, "Error", 5) == 0) {
6573         if (StrStr(message, "name") ||
6574             StrStr(message, "rating") || StrStr(message, "?") ||
6575             StrStr(message, "result") || StrStr(message, "board") ||
6576             StrStr(message, "bk") || StrStr(message, "computer") ||
6577             StrStr(message, "variant") || StrStr(message, "hint") ||
6578             StrStr(message, "random") || StrStr(message, "depth") ||
6579             StrStr(message, "accepted")) {
6580             return;
6581         }
6582         if (StrStr(message, "protover")) {
6583           /* Program is responding to input, so it's apparently done
6584              initializing, and this error message indicates it is
6585              protocol version 1.  So we don't need to wait any longer
6586              for it to initialize and send feature commands. */
6587           FeatureDone(cps, 1);
6588           cps->protocolVersion = 1;
6589           return;
6590         }
6591         cps->maybeThinking = FALSE;
6592
6593         if (StrStr(message, "draw")) {
6594             /* Program doesn't have "draw" command */
6595             cps->sendDrawOffers = 0;
6596             return;
6597         }
6598         if (cps->sendTime != 1 &&
6599             (StrStr(message, "time") || StrStr(message, "otim"))) {
6600           /* Program apparently doesn't have "time" or "otim" command */
6601           cps->sendTime = 0;
6602           return;
6603         }
6604         if (StrStr(message, "analyze")) {
6605             cps->analysisSupport = FALSE;
6606             cps->analyzing = FALSE;
6607             Reset(FALSE, TRUE);
6608             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6609             DisplayError(buf2, 0);
6610             return;
6611         }
6612         if (StrStr(message, "(no matching move)st")) {
6613           /* Special kludge for GNU Chess 4 only */
6614           cps->stKludge = TRUE;
6615           SendTimeControl(cps, movesPerSession, timeControl,
6616                           timeIncrement, appData.searchDepth,
6617                           searchTime);
6618           return;
6619         }
6620         if (StrStr(message, "(no matching move)sd")) {
6621           /* Special kludge for GNU Chess 4 only */
6622           cps->sdKludge = TRUE;
6623           SendTimeControl(cps, movesPerSession, timeControl,
6624                           timeIncrement, appData.searchDepth,
6625                           searchTime);
6626           return;
6627         }
6628         if (!StrStr(message, "llegal")) {
6629             return;
6630         }
6631         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6632             gameMode == IcsIdle) return;
6633         if (forwardMostMove <= backwardMostMove) return;
6634         if (pausing) PauseEvent();
6635       if(appData.forceIllegal) {
6636             // [HGM] illegal: machine refused move; force position after move into it
6637           SendToProgram("force\n", cps);
6638           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6639                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6640                 // when black is to move, while there might be nothing on a2 or black
6641                 // might already have the move. So send the board as if white has the move.
6642                 // But first we must change the stm of the engine, as it refused the last move
6643                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6644                 if(WhiteOnMove(forwardMostMove)) {
6645                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6646                     SendBoard(cps, forwardMostMove); // kludgeless board
6647                 } else {
6648                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6649                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6650                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6651                 }
6652           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6653             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6654                  gameMode == TwoMachinesPlay)
6655               SendToProgram("go\n", cps);
6656             return;
6657       } else
6658         if (gameMode == PlayFromGameFile) {
6659             /* Stop reading this game file */
6660             gameMode = EditGame;
6661             ModeHighlight();
6662         }
6663         currentMove = --forwardMostMove;
6664         DisplayMove(currentMove-1); /* before DisplayMoveError */
6665         SwitchClocks();
6666         DisplayBothClocks();
6667         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6668                 parseList[currentMove], cps->which);
6669         DisplayMoveError(buf1);
6670         DrawPosition(FALSE, boards[currentMove]);
6671
6672         /* [HGM] illegal-move claim should forfeit game when Xboard */
6673         /* only passes fully legal moves                            */
6674         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6675             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6676                                 "False illegal-move claim", GE_XBOARD );
6677         }
6678         return;
6679     }
6680     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6681         /* Program has a broken "time" command that
6682            outputs a string not ending in newline.
6683            Don't use it. */
6684         cps->sendTime = 0;
6685     }
6686
6687     /*
6688      * If chess program startup fails, exit with an error message.
6689      * Attempts to recover here are futile.
6690      */
6691     if ((StrStr(message, "unknown host") != NULL)
6692         || (StrStr(message, "No remote directory") != NULL)
6693         || (StrStr(message, "not found") != NULL)
6694         || (StrStr(message, "No such file") != NULL)
6695         || (StrStr(message, "can't alloc") != NULL)
6696         || (StrStr(message, "Permission denied") != NULL)) {
6697
6698         cps->maybeThinking = FALSE;
6699         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6700                 cps->which, cps->program, cps->host, message);
6701         RemoveInputSource(cps->isr);
6702         DisplayFatalError(buf1, 0, 1);
6703         return;
6704     }
6705
6706     /*
6707      * Look for hint output
6708      */
6709     if (sscanf(message, "Hint: %s", buf1) == 1) {
6710         if (cps == &first && hintRequested) {
6711             hintRequested = FALSE;
6712             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6713                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6714                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6715                                     PosFlags(forwardMostMove),
6716                                     fromY, fromX, toY, toX, promoChar, buf1);
6717                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6718                 DisplayInformation(buf2);
6719             } else {
6720                 /* Hint move could not be parsed!? */
6721               snprintf(buf2, sizeof(buf2),
6722                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6723                         buf1, cps->which);
6724                 DisplayError(buf2, 0);
6725             }
6726         } else {
6727             strcpy(lastHint, buf1);
6728         }
6729         return;
6730     }
6731
6732     /*
6733      * Ignore other messages if game is not in progress
6734      */
6735     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6736         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6737
6738     /*
6739      * look for win, lose, draw, or draw offer
6740      */
6741     if (strncmp(message, "1-0", 3) == 0) {
6742         char *p, *q, *r = "";
6743         p = strchr(message, '{');
6744         if (p) {
6745             q = strchr(p, '}');
6746             if (q) {
6747                 *q = NULLCHAR;
6748                 r = p + 1;
6749             }
6750         }
6751         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6752         return;
6753     } else if (strncmp(message, "0-1", 3) == 0) {
6754         char *p, *q, *r = "";
6755         p = strchr(message, '{');
6756         if (p) {
6757             q = strchr(p, '}');
6758             if (q) {
6759                 *q = NULLCHAR;
6760                 r = p + 1;
6761             }
6762         }
6763         /* Kludge for Arasan 4.1 bug */
6764         if (strcmp(r, "Black resigns") == 0) {
6765             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6766             return;
6767         }
6768         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6769         return;
6770     } else if (strncmp(message, "1/2", 3) == 0) {
6771         char *p, *q, *r = "";
6772         p = strchr(message, '{');
6773         if (p) {
6774             q = strchr(p, '}');
6775             if (q) {
6776                 *q = NULLCHAR;
6777                 r = p + 1;
6778             }
6779         }
6780
6781         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6782         return;
6783
6784     } else if (strncmp(message, "White resign", 12) == 0) {
6785         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6786         return;
6787     } else if (strncmp(message, "Black resign", 12) == 0) {
6788         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6789         return;
6790     } else if (strncmp(message, "White matches", 13) == 0 ||
6791                strncmp(message, "Black matches", 13) == 0   ) {
6792         /* [HGM] ignore GNUShogi noises */
6793         return;
6794     } else if (strncmp(message, "White", 5) == 0 &&
6795                message[5] != '(' &&
6796                StrStr(message, "Black") == NULL) {
6797         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6798         return;
6799     } else if (strncmp(message, "Black", 5) == 0 &&
6800                message[5] != '(') {
6801         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6802         return;
6803     } else if (strcmp(message, "resign") == 0 ||
6804                strcmp(message, "computer resigns") == 0) {
6805         switch (gameMode) {
6806           case MachinePlaysBlack:
6807           case IcsPlayingBlack:
6808             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6809             break;
6810           case MachinePlaysWhite:
6811           case IcsPlayingWhite:
6812             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6813             break;
6814           case TwoMachinesPlay:
6815             if (cps->twoMachinesColor[0] == 'w')
6816               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6817             else
6818               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6819             break;
6820           default:
6821             /* can't happen */
6822             break;
6823         }
6824         return;
6825     } else if (strncmp(message, "opponent mates", 14) == 0) {
6826         switch (gameMode) {
6827           case MachinePlaysBlack:
6828           case IcsPlayingBlack:
6829             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6830             break;
6831           case MachinePlaysWhite:
6832           case IcsPlayingWhite:
6833             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6834             break;
6835           case TwoMachinesPlay:
6836             if (cps->twoMachinesColor[0] == 'w')
6837               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6838             else
6839               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6840             break;
6841           default:
6842             /* can't happen */
6843             break;
6844         }
6845         return;
6846     } else if (strncmp(message, "computer mates", 14) == 0) {
6847         switch (gameMode) {
6848           case MachinePlaysBlack:
6849           case IcsPlayingBlack:
6850             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6851             break;
6852           case MachinePlaysWhite:
6853           case IcsPlayingWhite:
6854             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6855             break;
6856           case TwoMachinesPlay:
6857             if (cps->twoMachinesColor[0] == 'w')
6858               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6859             else
6860               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6861             break;
6862           default:
6863             /* can't happen */
6864             break;
6865         }
6866         return;
6867     } else if (strncmp(message, "checkmate", 9) == 0) {
6868         if (WhiteOnMove(forwardMostMove)) {
6869             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6870         } else {
6871             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6872         }
6873         return;
6874     } else if (strstr(message, "Draw") != NULL ||
6875                strstr(message, "game is a draw") != NULL) {
6876         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6877         return;
6878     } else if (strstr(message, "offer") != NULL &&
6879                strstr(message, "draw") != NULL) {
6880 #if ZIPPY
6881         if (appData.zippyPlay && first.initDone) {
6882             /* Relay offer to ICS */
6883             SendToICS(ics_prefix);
6884             SendToICS("draw\n");
6885         }
6886 #endif
6887         cps->offeredDraw = 2; /* valid until this engine moves twice */
6888         if (gameMode == TwoMachinesPlay) {
6889             if (cps->other->offeredDraw) {
6890                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6891             /* [HGM] in two-machine mode we delay relaying draw offer      */
6892             /* until after we also have move, to see if it is really claim */
6893             }
6894         } else if (gameMode == MachinePlaysWhite ||
6895                    gameMode == MachinePlaysBlack) {
6896           if (userOfferedDraw) {
6897             DisplayInformation(_("Machine accepts your draw offer"));
6898             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6899           } else {
6900             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6901           }
6902         }
6903     }
6904
6905
6906     /*
6907      * Look for thinking output
6908      */
6909     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6910           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6911                                 ) {
6912         int plylev, mvleft, mvtot, curscore, time;
6913         char mvname[MOVE_LEN];
6914         u64 nodes; // [DM]
6915         char plyext;
6916         int ignore = FALSE;
6917         int prefixHint = FALSE;
6918         mvname[0] = NULLCHAR;
6919
6920         switch (gameMode) {
6921           case MachinePlaysBlack:
6922           case IcsPlayingBlack:
6923             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6924             break;
6925           case MachinePlaysWhite:
6926           case IcsPlayingWhite:
6927             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6928             break;
6929           case AnalyzeMode:
6930           case AnalyzeFile:
6931             break;
6932           case IcsObserving: /* [DM] icsEngineAnalyze */
6933             if (!appData.icsEngineAnalyze) ignore = TRUE;
6934             break;
6935           case TwoMachinesPlay:
6936             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6937                 ignore = TRUE;
6938             }
6939             break;
6940           default:
6941             ignore = TRUE;
6942             break;
6943         }
6944
6945         if (!ignore) {
6946             buf1[0] = NULLCHAR;
6947             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6948                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6949
6950                 if (plyext != ' ' && plyext != '\t') {
6951                     time *= 100;
6952                 }
6953
6954                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6955                 if( cps->scoreIsAbsolute &&
6956                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6957                 {
6958                     curscore = -curscore;
6959                 }
6960
6961
6962                 programStats.depth = plylev;
6963                 programStats.nodes = nodes;
6964                 programStats.time = time;
6965                 programStats.score = curscore;
6966                 programStats.got_only_move = 0;
6967
6968                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6969                         int ticklen;
6970
6971                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6972                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6973                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6974                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6975                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6976                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6977                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6978                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6979                 }
6980
6981                 /* Buffer overflow protection */
6982                 if (buf1[0] != NULLCHAR) {
6983                     if (strlen(buf1) >= sizeof(programStats.movelist)
6984                         && appData.debugMode) {
6985                         fprintf(debugFP,
6986                                 "PV is too long; using the first %u bytes.\n",
6987                                 (unsigned) sizeof(programStats.movelist) - 1);
6988                     }
6989
6990                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6991                 } else {
6992                     sprintf(programStats.movelist, " no PV\n");
6993                 }
6994
6995                 if (programStats.seen_stat) {
6996                     programStats.ok_to_send = 1;
6997                 }
6998
6999                 if (strchr(programStats.movelist, '(') != NULL) {
7000                     programStats.line_is_book = 1;
7001                     programStats.nr_moves = 0;
7002                     programStats.moves_left = 0;
7003                 } else {
7004                     programStats.line_is_book = 0;
7005                 }
7006
7007                 SendProgramStatsToFrontend( cps, &programStats );
7008
7009                 /*
7010                     [AS] Protect the thinkOutput buffer from overflow... this
7011                     is only useful if buf1 hasn't overflowed first!
7012                 */
7013                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7014                         plylev,
7015                         (gameMode == TwoMachinesPlay ?
7016                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7017                         ((double) curscore) / 100.0,
7018                         prefixHint ? lastHint : "",
7019                         prefixHint ? " " : "" );
7020
7021                 if( buf1[0] != NULLCHAR ) {
7022                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7023
7024                     if( strlen(buf1) > max_len ) {
7025                         if( appData.debugMode) {
7026                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7027                         }
7028                         buf1[max_len+1] = '\0';
7029                     }
7030
7031                     strcat( thinkOutput, buf1 );
7032                 }
7033
7034                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7035                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7036                     DisplayMove(currentMove - 1);
7037                 }
7038                 return;
7039
7040             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7041                 /* crafty (9.25+) says "(only move) <move>"
7042                  * if there is only 1 legal move
7043                  */
7044                 sscanf(p, "(only move) %s", buf1);
7045                 sprintf(thinkOutput, "%s (only move)", buf1);
7046                 sprintf(programStats.movelist, "%s (only move)", buf1);
7047                 programStats.depth = 1;
7048                 programStats.nr_moves = 1;
7049                 programStats.moves_left = 1;
7050                 programStats.nodes = 1;
7051                 programStats.time = 1;
7052                 programStats.got_only_move = 1;
7053
7054                 /* Not really, but we also use this member to
7055                    mean "line isn't going to change" (Crafty
7056                    isn't searching, so stats won't change) */
7057                 programStats.line_is_book = 1;
7058
7059                 SendProgramStatsToFrontend( cps, &programStats );
7060
7061                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7062                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7063                     DisplayMove(currentMove - 1);
7064                 }
7065                 return;
7066             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7067                               &time, &nodes, &plylev, &mvleft,
7068                               &mvtot, mvname) >= 5) {
7069                 /* The stat01: line is from Crafty (9.29+) in response
7070                    to the "." command */
7071                 programStats.seen_stat = 1;
7072                 cps->maybeThinking = TRUE;
7073
7074                 if (programStats.got_only_move || !appData.periodicUpdates)
7075                   return;
7076
7077                 programStats.depth = plylev;
7078                 programStats.time = time;
7079                 programStats.nodes = nodes;
7080                 programStats.moves_left = mvleft;
7081                 programStats.nr_moves = mvtot;
7082                 strcpy(programStats.move_name, mvname);
7083                 programStats.ok_to_send = 1;
7084                 programStats.movelist[0] = '\0';
7085
7086                 SendProgramStatsToFrontend( cps, &programStats );
7087
7088                 return;
7089
7090             } else if (strncmp(message,"++",2) == 0) {
7091                 /* Crafty 9.29+ outputs this */
7092                 programStats.got_fail = 2;
7093                 return;
7094
7095             } else if (strncmp(message,"--",2) == 0) {
7096                 /* Crafty 9.29+ outputs this */
7097                 programStats.got_fail = 1;
7098                 return;
7099
7100             } else if (thinkOutput[0] != NULLCHAR &&
7101                        strncmp(message, "    ", 4) == 0) {
7102                 unsigned message_len;
7103
7104                 p = message;
7105                 while (*p && *p == ' ') p++;
7106
7107                 message_len = strlen( p );
7108
7109                 /* [AS] Avoid buffer overflow */
7110                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7111                     strcat(thinkOutput, " ");
7112                     strcat(thinkOutput, p);
7113                 }
7114
7115                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7116                     strcat(programStats.movelist, " ");
7117                     strcat(programStats.movelist, p);
7118                 }
7119
7120                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7121                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7122                     DisplayMove(currentMove - 1);
7123                 }
7124                 return;
7125             }
7126         }
7127         else {
7128             buf1[0] = NULLCHAR;
7129
7130             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7131                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7132             {
7133                 ChessProgramStats cpstats;
7134
7135                 if (plyext != ' ' && plyext != '\t') {
7136                     time *= 100;
7137                 }
7138
7139                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7140                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7141                     curscore = -curscore;
7142                 }
7143
7144                 cpstats.depth = plylev;
7145                 cpstats.nodes = nodes;
7146                 cpstats.time = time;
7147                 cpstats.score = curscore;
7148                 cpstats.got_only_move = 0;
7149                 cpstats.movelist[0] = '\0';
7150
7151                 if (buf1[0] != NULLCHAR) {
7152                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7153                 }
7154
7155                 cpstats.ok_to_send = 0;
7156                 cpstats.line_is_book = 0;
7157                 cpstats.nr_moves = 0;
7158                 cpstats.moves_left = 0;
7159
7160                 SendProgramStatsToFrontend( cps, &cpstats );
7161             }
7162         }
7163     }
7164 }
7165
7166
7167 /* Parse a game score from the character string "game", and
7168    record it as the history of the current game.  The game
7169    score is NOT assumed to start from the standard position.
7170    The display is not updated in any way.
7171    */
7172 void
7173 ParseGameHistory(game)
7174      char *game;
7175 {
7176     ChessMove moveType;
7177     int fromX, fromY, toX, toY, boardIndex;
7178     char promoChar;
7179     char *p, *q;
7180     char buf[MSG_SIZ];
7181
7182     if (appData.debugMode)
7183       fprintf(debugFP, "Parsing game history: %s\n", game);
7184
7185     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7186     gameInfo.site = StrSave(appData.icsHost);
7187     gameInfo.date = PGNDate();
7188     gameInfo.round = StrSave("-");
7189
7190     /* Parse out names of players */
7191     while (*game == ' ') game++;
7192     p = buf;
7193     while (*game != ' ') *p++ = *game++;
7194     *p = NULLCHAR;
7195     gameInfo.white = StrSave(buf);
7196     while (*game == ' ') game++;
7197     p = buf;
7198     while (*game != ' ' && *game != '\n') *p++ = *game++;
7199     *p = NULLCHAR;
7200     gameInfo.black = StrSave(buf);
7201
7202     /* Parse moves */
7203     boardIndex = blackPlaysFirst ? 1 : 0;
7204     yynewstr(game);
7205     for (;;) {
7206         yyboardindex = boardIndex;
7207         moveType = (ChessMove) yylex();
7208         switch (moveType) {
7209           case IllegalMove:             /* maybe suicide chess, etc. */
7210   if (appData.debugMode) {
7211     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7212     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7213     setbuf(debugFP, NULL);
7214   }
7215           case WhitePromotionChancellor:
7216           case BlackPromotionChancellor:
7217           case WhitePromotionArchbishop:
7218           case BlackPromotionArchbishop:
7219           case WhitePromotionQueen:
7220           case BlackPromotionQueen:
7221           case WhitePromotionRook:
7222           case BlackPromotionRook:
7223           case WhitePromotionBishop:
7224           case BlackPromotionBishop:
7225           case WhitePromotionKnight:
7226           case BlackPromotionKnight:
7227           case WhitePromotionKing:
7228           case BlackPromotionKing:
7229           case NormalMove:
7230           case WhiteCapturesEnPassant:
7231           case BlackCapturesEnPassant:
7232           case WhiteKingSideCastle:
7233           case WhiteQueenSideCastle:
7234           case BlackKingSideCastle:
7235           case BlackQueenSideCastle:
7236           case WhiteKingSideCastleWild:
7237           case WhiteQueenSideCastleWild:
7238           case BlackKingSideCastleWild:
7239           case BlackQueenSideCastleWild:
7240           /* PUSH Fabien */
7241           case WhiteHSideCastleFR:
7242           case WhiteASideCastleFR:
7243           case BlackHSideCastleFR:
7244           case BlackASideCastleFR:
7245           /* POP Fabien */
7246             fromX = currentMoveString[0] - AAA;
7247             fromY = currentMoveString[1] - ONE;
7248             toX = currentMoveString[2] - AAA;
7249             toY = currentMoveString[3] - ONE;
7250             promoChar = currentMoveString[4];
7251             break;
7252           case WhiteDrop:
7253           case BlackDrop:
7254             fromX = moveType == WhiteDrop ?
7255               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7256             (int) CharToPiece(ToLower(currentMoveString[0]));
7257             fromY = DROP_RANK;
7258             toX = currentMoveString[2] - AAA;
7259             toY = currentMoveString[3] - ONE;
7260             promoChar = NULLCHAR;
7261             break;
7262           case AmbiguousMove:
7263             /* bug? */
7264             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7265   if (appData.debugMode) {
7266     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7267     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7268     setbuf(debugFP, NULL);
7269   }
7270             DisplayError(buf, 0);
7271             return;
7272           case ImpossibleMove:
7273             /* bug? */
7274             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7275   if (appData.debugMode) {
7276     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7277     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7278     setbuf(debugFP, NULL);
7279   }
7280             DisplayError(buf, 0);
7281             return;
7282           case (ChessMove) 0:   /* end of file */
7283             if (boardIndex < backwardMostMove) {
7284                 /* Oops, gap.  How did that happen? */
7285                 DisplayError(_("Gap in move list"), 0);
7286                 return;
7287             }
7288             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7289             if (boardIndex > forwardMostMove) {
7290                 forwardMostMove = boardIndex;
7291             }
7292             return;
7293           case ElapsedTime:
7294             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7295                 strcat(parseList[boardIndex-1], " ");
7296                 strcat(parseList[boardIndex-1], yy_text);
7297             }
7298             continue;
7299           case Comment:
7300           case PGNTag:
7301           case NAG:
7302           default:
7303             /* ignore */
7304             continue;
7305           case WhiteWins:
7306           case BlackWins:
7307           case GameIsDrawn:
7308           case GameUnfinished:
7309             if (gameMode == IcsExamining) {
7310                 if (boardIndex < backwardMostMove) {
7311                     /* Oops, gap.  How did that happen? */
7312                     return;
7313                 }
7314                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7315                 return;
7316             }
7317             gameInfo.result = moveType;
7318             p = strchr(yy_text, '{');
7319             if (p == NULL) p = strchr(yy_text, '(');
7320             if (p == NULL) {
7321                 p = yy_text;
7322                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7323             } else {
7324                 q = strchr(p, *p == '{' ? '}' : ')');
7325                 if (q != NULL) *q = NULLCHAR;
7326                 p++;
7327             }
7328             gameInfo.resultDetails = StrSave(p);
7329             continue;
7330         }
7331         if (boardIndex >= forwardMostMove &&
7332             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7333             backwardMostMove = blackPlaysFirst ? 1 : 0;
7334             return;
7335         }
7336         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7337                                  fromY, fromX, toY, toX, promoChar,
7338                                  parseList[boardIndex]);
7339         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7340         /* currentMoveString is set as a side-effect of yylex */
7341         strcpy(moveList[boardIndex], currentMoveString);
7342         strcat(moveList[boardIndex], "\n");
7343         boardIndex++;
7344         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7345         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7346           case MT_NONE:
7347           case MT_STALEMATE:
7348           default:
7349             break;
7350           case MT_CHECK:
7351             if(gameInfo.variant != VariantShogi)
7352                 strcat(parseList[boardIndex - 1], "+");
7353             break;
7354           case MT_CHECKMATE:
7355           case MT_STAINMATE:
7356             strcat(parseList[boardIndex - 1], "#");
7357             break;
7358         }
7359     }
7360 }
7361
7362
7363 /* Apply a move to the given board  */
7364 void
7365 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7366      int fromX, fromY, toX, toY;
7367      int promoChar;
7368      Board board;
7369 {
7370   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7371
7372     /* [HGM] compute & store e.p. status and castling rights for new position */
7373     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7374     { int i;
7375
7376       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7377       oldEP = (signed char)board[EP_STATUS];
7378       board[EP_STATUS] = EP_NONE;
7379
7380       if( board[toY][toX] != EmptySquare ) 
7381            board[EP_STATUS] = EP_CAPTURE;  
7382
7383       if( board[fromY][fromX] == WhitePawn ) {
7384            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7385                board[EP_STATUS] = EP_PAWN_MOVE;
7386            if( toY-fromY==2) {
7387                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7388                         gameInfo.variant != VariantBerolina || toX < fromX)
7389                       board[EP_STATUS] = toX | berolina;
7390                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7391                   gameInfo.variant != VariantBerolina || toX > fromX) 
7392                  board[EP_STATUS] = toX;
7393            }
7394       } else
7395       if( board[fromY][fromX] == BlackPawn ) {
7396            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7397                board[EP_STATUS] = EP_PAWN_MOVE; 
7398            if( toY-fromY== -2) {
7399                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7400                         gameInfo.variant != VariantBerolina || toX < fromX)
7401                       board[EP_STATUS] = toX | berolina;
7402                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7403                         gameInfo.variant != VariantBerolina || toX > fromX) 
7404                       board[EP_STATUS] = toX;
7405            }
7406        }
7407
7408        for(i=0; i<nrCastlingRights; i++) {
7409            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7410               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7411              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7412        }
7413
7414     }
7415
7416   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7417   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7418        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7419
7420   if (fromX == toX && fromY == toY) return;
7421
7422   if (fromY == DROP_RANK) {
7423         /* must be first */
7424         piece = board[toY][toX] = (ChessSquare) fromX;
7425   } else {
7426      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7427      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7428      if(gameInfo.variant == VariantKnightmate)
7429          king += (int) WhiteUnicorn - (int) WhiteKing;
7430
7431     /* Code added by Tord: */
7432     /* FRC castling assumed when king captures friendly rook. */
7433     if (board[fromY][fromX] == WhiteKing &&
7434              board[toY][toX] == WhiteRook) {
7435       board[fromY][fromX] = EmptySquare;
7436       board[toY][toX] = EmptySquare;
7437       if(toX > fromX) {
7438         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7439       } else {
7440         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7441       }
7442     } else if (board[fromY][fromX] == BlackKing &&
7443                board[toY][toX] == BlackRook) {
7444       board[fromY][fromX] = EmptySquare;
7445       board[toY][toX] = EmptySquare;
7446       if(toX > fromX) {
7447         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7448       } else {
7449         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7450       }
7451     /* End of code added by Tord */
7452
7453     } else if (board[fromY][fromX] == king
7454         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7455         && toY == fromY && toX > fromX+1) {
7456         board[fromY][fromX] = EmptySquare;
7457         board[toY][toX] = king;
7458         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7459         board[fromY][BOARD_RGHT-1] = EmptySquare;
7460     } else if (board[fromY][fromX] == king
7461         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7462                && toY == fromY && toX < fromX-1) {
7463         board[fromY][fromX] = EmptySquare;
7464         board[toY][toX] = king;
7465         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7466         board[fromY][BOARD_LEFT] = EmptySquare;
7467     } else if (board[fromY][fromX] == WhitePawn
7468                && toY == BOARD_HEIGHT-1
7469                && gameInfo.variant != VariantXiangqi
7470                ) {
7471         /* white pawn promotion */
7472         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7473         if (board[toY][toX] == EmptySquare) {
7474             board[toY][toX] = WhiteQueen;
7475         }
7476         if(gameInfo.variant==VariantBughouse ||
7477            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7478             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7479         board[fromY][fromX] = EmptySquare;
7480     } else if ((fromY == BOARD_HEIGHT-4)
7481                && (toX != fromX)
7482                && gameInfo.variant != VariantXiangqi
7483                && gameInfo.variant != VariantBerolina
7484                && (board[fromY][fromX] == WhitePawn)
7485                && (board[toY][toX] == EmptySquare)) {
7486         board[fromY][fromX] = EmptySquare;
7487         board[toY][toX] = WhitePawn;
7488         captured = board[toY - 1][toX];
7489         board[toY - 1][toX] = EmptySquare;
7490     } else if ((fromY == BOARD_HEIGHT-4)
7491                && (toX == fromX)
7492                && gameInfo.variant == VariantBerolina
7493                && (board[fromY][fromX] == WhitePawn)
7494                && (board[toY][toX] == EmptySquare)) {
7495         board[fromY][fromX] = EmptySquare;
7496         board[toY][toX] = WhitePawn;
7497         if(oldEP & EP_BEROLIN_A) {
7498                 captured = board[fromY][fromX-1];
7499                 board[fromY][fromX-1] = EmptySquare;
7500         }else{  captured = board[fromY][fromX+1];
7501                 board[fromY][fromX+1] = EmptySquare;
7502         }
7503     } else if (board[fromY][fromX] == king
7504         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7505                && toY == fromY && toX > fromX+1) {
7506         board[fromY][fromX] = EmptySquare;
7507         board[toY][toX] = king;
7508         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7509         board[fromY][BOARD_RGHT-1] = EmptySquare;
7510     } else if (board[fromY][fromX] == king
7511         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7512                && toY == fromY && toX < fromX-1) {
7513         board[fromY][fromX] = EmptySquare;
7514         board[toY][toX] = king;
7515         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7516         board[fromY][BOARD_LEFT] = EmptySquare;
7517     } else if (fromY == 7 && fromX == 3
7518                && board[fromY][fromX] == BlackKing
7519                && toY == 7 && toX == 5) {
7520         board[fromY][fromX] = EmptySquare;
7521         board[toY][toX] = BlackKing;
7522         board[fromY][7] = EmptySquare;
7523         board[toY][4] = BlackRook;
7524     } else if (fromY == 7 && fromX == 3
7525                && board[fromY][fromX] == BlackKing
7526                && toY == 7 && toX == 1) {
7527         board[fromY][fromX] = EmptySquare;
7528         board[toY][toX] = BlackKing;
7529         board[fromY][0] = EmptySquare;
7530         board[toY][2] = BlackRook;
7531     } else if (board[fromY][fromX] == BlackPawn
7532                && toY == 0
7533                && gameInfo.variant != VariantXiangqi
7534                ) {
7535         /* black pawn promotion */
7536         board[0][toX] = CharToPiece(ToLower(promoChar));
7537         if (board[0][toX] == EmptySquare) {
7538             board[0][toX] = BlackQueen;
7539         }
7540         if(gameInfo.variant==VariantBughouse ||
7541            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7542             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7543         board[fromY][fromX] = EmptySquare;
7544     } else if ((fromY == 3)
7545                && (toX != fromX)
7546                && gameInfo.variant != VariantXiangqi
7547                && gameInfo.variant != VariantBerolina
7548                && (board[fromY][fromX] == BlackPawn)
7549                && (board[toY][toX] == EmptySquare)) {
7550         board[fromY][fromX] = EmptySquare;
7551         board[toY][toX] = BlackPawn;
7552         captured = board[toY + 1][toX];
7553         board[toY + 1][toX] = EmptySquare;
7554     } else if ((fromY == 3)
7555                && (toX == fromX)
7556                && gameInfo.variant == VariantBerolina
7557                && (board[fromY][fromX] == BlackPawn)
7558                && (board[toY][toX] == EmptySquare)) {
7559         board[fromY][fromX] = EmptySquare;
7560         board[toY][toX] = BlackPawn;
7561         if(oldEP & EP_BEROLIN_A) {
7562                 captured = board[fromY][fromX-1];
7563                 board[fromY][fromX-1] = EmptySquare;
7564         }else{  captured = board[fromY][fromX+1];
7565                 board[fromY][fromX+1] = EmptySquare;
7566         }
7567     } else {
7568         board[toY][toX] = board[fromY][fromX];
7569         board[fromY][fromX] = EmptySquare;
7570     }
7571
7572     /* [HGM] now we promote for Shogi, if needed */
7573     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7574         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7575   }
7576
7577     if (gameInfo.holdingsWidth != 0) {
7578
7579       /* !!A lot more code needs to be written to support holdings  */
7580       /* [HGM] OK, so I have written it. Holdings are stored in the */
7581       /* penultimate board files, so they are automaticlly stored   */
7582       /* in the game history.                                       */
7583       if (fromY == DROP_RANK) {
7584         /* Delete from holdings, by decreasing count */
7585         /* and erasing image if necessary            */
7586         p = (int) fromX;
7587         if(p < (int) BlackPawn) { /* white drop */
7588              p -= (int)WhitePawn;
7589                  p = PieceToNumber((ChessSquare)p);
7590              if(p >= gameInfo.holdingsSize) p = 0;
7591              if(--board[p][BOARD_WIDTH-2] <= 0)
7592                   board[p][BOARD_WIDTH-1] = EmptySquare;
7593              if((int)board[p][BOARD_WIDTH-2] < 0)
7594                         board[p][BOARD_WIDTH-2] = 0;
7595         } else {                  /* black drop */
7596              p -= (int)BlackPawn;
7597                  p = PieceToNumber((ChessSquare)p);
7598              if(p >= gameInfo.holdingsSize) p = 0;
7599              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7600                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7601              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7602                         board[BOARD_HEIGHT-1-p][1] = 0;
7603         }
7604       }
7605       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7606           && gameInfo.variant != VariantBughouse        ) {
7607         /* [HGM] holdings: Add to holdings, if holdings exist */
7608         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7609                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7610                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7611         }
7612         p = (int) captured;
7613         if (p >= (int) BlackPawn) {
7614           p -= (int)BlackPawn;
7615           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7616                   /* in Shogi restore piece to its original  first */
7617                   captured = (ChessSquare) (DEMOTED captured);
7618                   p = DEMOTED p;
7619           }
7620           p = PieceToNumber((ChessSquare)p);
7621           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7622           board[p][BOARD_WIDTH-2]++;
7623           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7624         } else {
7625           p -= (int)WhitePawn;
7626           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7627                   captured = (ChessSquare) (DEMOTED captured);
7628                   p = DEMOTED p;
7629           }
7630           p = PieceToNumber((ChessSquare)p);
7631           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7632           board[BOARD_HEIGHT-1-p][1]++;
7633           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7634         }
7635       }
7636     } else if (gameInfo.variant == VariantAtomic) {
7637       if (captured != EmptySquare) {
7638         int y, x;
7639         for (y = toY-1; y <= toY+1; y++) {
7640           for (x = toX-1; x <= toX+1; x++) {
7641             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7642                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7643               board[y][x] = EmptySquare;
7644             }
7645           }
7646         }
7647         board[toY][toX] = EmptySquare;
7648       }
7649     }
7650     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7651         /* [HGM] Shogi promotions */
7652         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7653     }
7654
7655     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7656                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7657         // [HGM] superchess: take promotion piece out of holdings
7658         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7659         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7660             if(!--board[k][BOARD_WIDTH-2])
7661                 board[k][BOARD_WIDTH-1] = EmptySquare;
7662         } else {
7663             if(!--board[BOARD_HEIGHT-1-k][1])
7664                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7665         }
7666     }
7667
7668 }
7669
7670 /* Updates forwardMostMove */
7671 void
7672 MakeMove(fromX, fromY, toX, toY, promoChar)
7673      int fromX, fromY, toX, toY;
7674      int promoChar;
7675 {
7676 //    forwardMostMove++; // [HGM] bare: moved downstream
7677
7678     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7679         int timeLeft; static int lastLoadFlag=0; int king, piece;
7680         piece = boards[forwardMostMove][fromY][fromX];
7681         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7682         if(gameInfo.variant == VariantKnightmate)
7683             king += (int) WhiteUnicorn - (int) WhiteKing;
7684         if(forwardMostMove == 0) {
7685             if(blackPlaysFirst)
7686                 fprintf(serverMoves, "%s;", second.tidy);
7687             fprintf(serverMoves, "%s;", first.tidy);
7688             if(!blackPlaysFirst)
7689                 fprintf(serverMoves, "%s;", second.tidy);
7690         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7691         lastLoadFlag = loadFlag;
7692         // print base move
7693         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7694         // print castling suffix
7695         if( toY == fromY && piece == king ) {
7696             if(toX-fromX > 1)
7697                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7698             if(fromX-toX >1)
7699                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7700         }
7701         // e.p. suffix
7702         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7703              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7704              boards[forwardMostMove][toY][toX] == EmptySquare
7705              && fromX != toX )
7706                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7707         // promotion suffix
7708         if(promoChar != NULLCHAR)
7709                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7710         if(!loadFlag) {
7711             fprintf(serverMoves, "/%d/%d",
7712                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7713             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7714             else                      timeLeft = blackTimeRemaining/1000;
7715             fprintf(serverMoves, "/%d", timeLeft);
7716         }
7717         fflush(serverMoves);
7718     }
7719
7720     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7721       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7722                         0, 1);
7723       return;
7724     }
7725     if (commentList[forwardMostMove+1] != NULL) {
7726         free(commentList[forwardMostMove+1]);
7727         commentList[forwardMostMove+1] = NULL;
7728     }
7729     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7730     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7731     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7732     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7733     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7734     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7735     gameInfo.result = GameUnfinished;
7736     if (gameInfo.resultDetails != NULL) {
7737         free(gameInfo.resultDetails);
7738         gameInfo.resultDetails = NULL;
7739     }
7740     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7741                               moveList[forwardMostMove - 1]);
7742     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7743                              PosFlags(forwardMostMove - 1),
7744                              fromY, fromX, toY, toX, promoChar,
7745                              parseList[forwardMostMove - 1]);
7746     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7747       case MT_NONE:
7748       case MT_STALEMATE:
7749       default:
7750         break;
7751       case MT_CHECK:
7752         if(gameInfo.variant != VariantShogi)
7753             strcat(parseList[forwardMostMove - 1], "+");
7754         break;
7755       case MT_CHECKMATE:
7756       case MT_STAINMATE:
7757         strcat(parseList[forwardMostMove - 1], "#");
7758         break;
7759     }
7760     if (appData.debugMode) {
7761         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7762     }
7763
7764 }
7765
7766 /* Updates currentMove if not pausing */
7767 void
7768 ShowMove(fromX, fromY, toX, toY)
7769 {
7770     int instant = (gameMode == PlayFromGameFile) ?
7771         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7772
7773     if(appData.noGUI) return;
7774
7775     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7776       {
7777         if (!instant)
7778           {
7779             if (forwardMostMove == currentMove + 1)
7780               {
7781 //TODO
7782 //              AnimateMove(boards[forwardMostMove - 1],
7783 //                          fromX, fromY, toX, toY);
7784               }
7785             if (appData.highlightLastMove)
7786               {
7787                 SetHighlights(fromX, fromY, toX, toY);
7788               }
7789           }
7790         currentMove = forwardMostMove;
7791     }
7792
7793     if (instant) return;
7794
7795     DisplayMove(currentMove - 1);
7796     DrawPosition(FALSE, boards[currentMove]);
7797     DisplayBothClocks();
7798     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7799
7800     return;
7801 }
7802
7803 void SendEgtPath(ChessProgramState *cps)
7804 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7805         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7806
7807         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7808
7809         while(*p) {
7810             char c, *q = name+1, *r, *s;
7811
7812             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7813             while(*p && *p != ',') *q++ = *p++;
7814             *q++ = ':'; *q = 0;
7815             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7816                 strcmp(name, ",nalimov:") == 0 ) {
7817                 // take nalimov path from the menu-changeable option first, if it is defined
7818                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7819                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7820             } else
7821             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7822                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7823                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7824                 s = r = StrStr(s, ":") + 1; // beginning of path info
7825                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7826                 c = *r; *r = 0;             // temporarily null-terminate path info
7827                     *--q = 0;               // strip of trailig ':' from name
7828                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7829                 *r = c;
7830                 SendToProgram(buf,cps);     // send egtbpath command for this format
7831             }
7832             if(*p == ',') p++; // read away comma to position for next format name
7833         }
7834 }
7835
7836 void
7837 InitChessProgram(cps, setup)
7838      ChessProgramState *cps;
7839      int setup; /* [HGM] needed to setup FRC opening position */
7840 {
7841     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7842     if (appData.noChessProgram) return;
7843     hintRequested = FALSE;
7844     bookRequested = FALSE;
7845
7846     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7847     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7848     if(cps->memSize) { /* [HGM] memory */
7849         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7850         SendToProgram(buf, cps);
7851     }
7852     SendEgtPath(cps); /* [HGM] EGT */
7853     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7854         sprintf(buf, "cores %d\n", appData.smpCores);
7855         SendToProgram(buf, cps);
7856     }
7857
7858     SendToProgram(cps->initString, cps);
7859     if (gameInfo.variant != VariantNormal &&
7860         gameInfo.variant != VariantLoadable
7861         /* [HGM] also send variant if board size non-standard */
7862         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7863                                             ) {
7864       char *v = VariantName(gameInfo.variant);
7865       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7866         /* [HGM] in protocol 1 we have to assume all variants valid */
7867         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7868         DisplayFatalError(buf, 0, 1);
7869         return;
7870       }
7871
7872       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7873       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7874       if( gameInfo.variant == VariantXiangqi )
7875            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7876       if( gameInfo.variant == VariantShogi )
7877            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7878       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7879            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7880       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7881                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7882            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7883       if( gameInfo.variant == VariantCourier )
7884            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7885       if( gameInfo.variant == VariantSuper )
7886            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7887       if( gameInfo.variant == VariantGreat )
7888            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7889
7890       if(overruled) {
7891            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7892                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7893            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7894            if(StrStr(cps->variants, b) == NULL) {
7895                // specific sized variant not known, check if general sizing allowed
7896                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7897                    if(StrStr(cps->variants, "boardsize") == NULL) {
7898                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7899                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7900                        DisplayFatalError(buf, 0, 1);
7901                        return;
7902                    }
7903                    /* [HGM] here we really should compare with the maximum supported board size */
7904                }
7905            }
7906       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7907       sprintf(buf, "variant %s\n", b);
7908       SendToProgram(buf, cps);
7909     }
7910     currentlyInitializedVariant = gameInfo.variant;
7911
7912     /* [HGM] send opening position in FRC to first engine */
7913     if(setup) {
7914           SendToProgram("force\n", cps);
7915           SendBoard(cps, 0);
7916           /* engine is now in force mode! Set flag to wake it up after first move. */
7917           setboardSpoiledMachineBlack = 1;
7918     }
7919
7920     if (cps->sendICS) {
7921       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7922       SendToProgram(buf, cps);
7923     }
7924     cps->maybeThinking = FALSE;
7925     cps->offeredDraw = 0;
7926     if (!appData.icsActive) {
7927         SendTimeControl(cps, movesPerSession, timeControl,
7928                         timeIncrement, appData.searchDepth,
7929                         searchTime);
7930     }
7931     if (appData.showThinking
7932         // [HGM] thinking: four options require thinking output to be sent
7933         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7934                                 ) {
7935         SendToProgram("post\n", cps);
7936     }
7937     SendToProgram("hard\n", cps);
7938     if (!appData.ponderNextMove) {
7939         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7940            it without being sure what state we are in first.  "hard"
7941            is not a toggle, so that one is OK.
7942          */
7943         SendToProgram("easy\n", cps);
7944     }
7945     if (cps->usePing) {
7946       sprintf(buf, "ping %d\n", ++cps->lastPing);
7947       SendToProgram(buf, cps);
7948     }
7949     cps->initDone = TRUE;
7950 }
7951
7952
7953 void
7954 StartChessProgram(cps)
7955      ChessProgramState *cps;
7956 {
7957     char buf[MSG_SIZ];
7958     int err;
7959
7960     if (appData.noChessProgram) return;
7961     cps->initDone = FALSE;
7962
7963     if (strcmp(cps->host, "localhost") == 0) {
7964         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7965     } else if (*appData.remoteShell == NULLCHAR) {
7966         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7967     } else {
7968         if (*appData.remoteUser == NULLCHAR) {
7969           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7970                     cps->program);
7971         } else {
7972           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7973                     cps->host, appData.remoteUser, cps->program);
7974         }
7975         err = StartChildProcess(buf, "", &cps->pr);
7976     }
7977
7978     if (err != 0) {
7979         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7980         DisplayFatalError(buf, err, 1);
7981         cps->pr = NoProc;
7982         cps->isr = NULL;
7983         return;
7984     }
7985
7986     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7987     if (cps->protocolVersion > 1) {
7988       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7989       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7990       cps->comboCnt = 0;  //                and values of combo boxes
7991       SendToProgram(buf, cps);
7992     } else {
7993       SendToProgram("xboard\n", cps);
7994     }
7995 }
7996
7997
7998 void
7999 TwoMachinesEventIfReady P((void))
8000 {
8001   if (first.lastPing != first.lastPong) {
8002     DisplayMessage("", _("Waiting for first chess program"));
8003     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8004     return;
8005   }
8006   if (second.lastPing != second.lastPong) {
8007     DisplayMessage("", _("Waiting for second chess program"));
8008     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8009     return;
8010   }
8011   ThawUI();
8012   TwoMachinesEvent();
8013 }
8014
8015 void
8016 NextMatchGame P((void))
8017 {
8018     int index; /* [HGM] autoinc: step load index during match */
8019     Reset(FALSE, TRUE);
8020     if (*appData.loadGameFile != NULLCHAR) {
8021         index = appData.loadGameIndex;
8022         if(index < 0) { // [HGM] autoinc
8023             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8024             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8025         }
8026         LoadGameFromFile(appData.loadGameFile,
8027                          index,
8028                          appData.loadGameFile, FALSE);
8029     } else if (*appData.loadPositionFile != NULLCHAR) {
8030         index = appData.loadPositionIndex;
8031         if(index < 0) { // [HGM] autoinc
8032             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8033             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8034         }
8035         LoadPositionFromFile(appData.loadPositionFile,
8036                              index,
8037                              appData.loadPositionFile);
8038     }
8039     TwoMachinesEventIfReady();
8040 }
8041
8042 void UserAdjudicationEvent( int result )
8043 {
8044     ChessMove gameResult = GameIsDrawn;
8045
8046     if( result > 0 ) {
8047         gameResult = WhiteWins;
8048     }
8049     else if( result < 0 ) {
8050         gameResult = BlackWins;
8051     }
8052
8053     if( gameMode == TwoMachinesPlay ) {
8054         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8055     }
8056 }
8057
8058
8059 // [HGM] save: calculate checksum of game to make games easily identifiable
8060 int StringCheckSum(char *s)
8061 {
8062         int i = 0;
8063         if(s==NULL) return 0;
8064         while(*s) i = i*259 + *s++;
8065         return i;
8066 }
8067
8068 int GameCheckSum()
8069 {
8070         int i, sum=0;
8071         for(i=backwardMostMove; i<forwardMostMove; i++) {
8072                 sum += pvInfoList[i].depth;
8073                 sum += StringCheckSum(parseList[i]);
8074                 sum += StringCheckSum(commentList[i]);
8075                 sum *= 261;
8076         }
8077         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8078         return sum + StringCheckSum(commentList[i]);
8079 } // end of save patch
8080
8081 void
8082 GameEnds(result, resultDetails, whosays)
8083      ChessMove result;
8084      char *resultDetails;
8085      int whosays;
8086 {
8087     GameMode nextGameMode;
8088     int isIcsGame;
8089     char buf[MSG_SIZ];
8090
8091     if(endingGame) return; /* [HGM] crash: forbid recursion */
8092     endingGame = 1;
8093
8094     if (appData.debugMode) {
8095       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8096               result, resultDetails ? resultDetails : "(null)", whosays);
8097     }
8098
8099     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8100         /* If we are playing on ICS, the server decides when the
8101            game is over, but the engine can offer to draw, claim
8102            a draw, or resign.
8103          */
8104 #if ZIPPY
8105         if (appData.zippyPlay && first.initDone) {
8106             if (result == GameIsDrawn) {
8107                 /* In case draw still needs to be claimed */
8108                 SendToICS(ics_prefix);
8109                 SendToICS("draw\n");
8110             } else if (StrCaseStr(resultDetails, "resign")) {
8111                 SendToICS(ics_prefix);
8112                 SendToICS("resign\n");
8113             }
8114         }
8115 #endif
8116         endingGame = 0; /* [HGM] crash */
8117         return;
8118     }
8119
8120     /* If we're loading the game from a file, stop */
8121     if (whosays == GE_FILE) {
8122       (void) StopLoadGameTimer();
8123       gameFileFP = NULL;
8124     }
8125
8126     /* Cancel draw offers */
8127     first.offeredDraw = second.offeredDraw = 0;
8128
8129     /* If this is an ICS game, only ICS can really say it's done;
8130        if not, anyone can. */
8131     isIcsGame = (gameMode == IcsPlayingWhite ||
8132                  gameMode == IcsPlayingBlack ||
8133                  gameMode == IcsObserving    ||
8134                  gameMode == IcsExamining);
8135
8136     if (!isIcsGame || whosays == GE_ICS) {
8137         /* OK -- not an ICS game, or ICS said it was done */
8138         StopClocks();
8139         if (!isIcsGame && !appData.noChessProgram)
8140           SetUserThinkingEnables();
8141
8142         /* [HGM] if a machine claims the game end we verify this claim */
8143         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8144             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8145                 char claimer;
8146                 ChessMove trueResult = (ChessMove) -1;
8147
8148                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8149                                             first.twoMachinesColor[0] :
8150                                             second.twoMachinesColor[0] ;
8151
8152                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8153                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8154                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8155                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8156                 } else
8157                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8158                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8159                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8160                 } else
8161                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8162                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8163                 }
8164
8165                 // now verify win claims, but not in drop games, as we don't understand those yet
8166                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8167                                                  || gameInfo.variant == VariantGreat) &&
8168                     (result == WhiteWins && claimer == 'w' ||
8169                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8170                       if (appData.debugMode) {
8171                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8172                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8173                       }
8174                       if(result != trueResult) {
8175                               sprintf(buf, "False win claim: '%s'", resultDetails);
8176                               result = claimer == 'w' ? BlackWins : WhiteWins;
8177                               resultDetails = buf;
8178                       }
8179                 } else
8180                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8181                     && (forwardMostMove <= backwardMostMove ||
8182                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8183                         (claimer=='b')==(forwardMostMove&1))
8184                                                                                   ) {
8185                       /* [HGM] verify: draws that were not flagged are false claims */
8186                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8187                       result = claimer == 'w' ? BlackWins : WhiteWins;
8188                       resultDetails = buf;
8189                 }
8190                 /* (Claiming a loss is accepted no questions asked!) */
8191             }
8192
8193             /* [HGM] bare: don't allow bare King to win */
8194             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8195                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8196                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8197                && result != GameIsDrawn)
8198             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8199                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8200                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8201                         if(p >= 0 && p <= (int)WhiteKing) k++;
8202                 }
8203                 if (appData.debugMode) {
8204                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8205                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8206                 }
8207                 if(k <= 1) {
8208                         result = GameIsDrawn;
8209                         sprintf(buf, "%s but bare king", resultDetails);
8210                         resultDetails = buf;
8211                 }
8212             }
8213         }
8214
8215         if(serverMoves != NULL && !loadFlag) { char c = '=';
8216             if(result==WhiteWins) c = '+';
8217             if(result==BlackWins) c = '-';
8218             if(resultDetails != NULL)
8219                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8220         }
8221         if (resultDetails != NULL) {
8222             gameInfo.result = result;
8223             gameInfo.resultDetails = StrSave(resultDetails);
8224
8225             /* display last move only if game was not loaded from file */
8226             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8227                 DisplayMove(currentMove - 1);
8228
8229             if (forwardMostMove != 0) {
8230                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8231                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8232                                                                 ) {
8233                     if (*appData.saveGameFile != NULLCHAR) {
8234                         SaveGameToFile(appData.saveGameFile, TRUE);
8235                     } else if (appData.autoSaveGames) {
8236                         AutoSaveGame();
8237                     }
8238                     if (*appData.savePositionFile != NULLCHAR) {
8239                         SavePositionToFile(appData.savePositionFile);
8240                     }
8241                 }
8242             }
8243
8244             /* Tell program how game ended in case it is learning */
8245             /* [HGM] Moved this to after saving the PGN, just in case */
8246             /* engine died and we got here through time loss. In that */
8247             /* case we will get a fatal error writing the pipe, which */
8248             /* would otherwise lose us the PGN.                       */
8249             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8250             /* output during GameEnds should never be fatal anymore   */
8251             if (gameMode == MachinePlaysWhite ||
8252                 gameMode == MachinePlaysBlack ||
8253                 gameMode == TwoMachinesPlay ||
8254                 gameMode == IcsPlayingWhite ||
8255                 gameMode == IcsPlayingBlack ||
8256                 gameMode == BeginningOfGame) {
8257                 char buf[MSG_SIZ];
8258                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8259                         resultDetails);
8260                 if (first.pr != NoProc) {
8261                     SendToProgram(buf, &first);
8262                 }
8263                 if (second.pr != NoProc &&
8264                     gameMode == TwoMachinesPlay) {
8265                     SendToProgram(buf, &second);
8266                 }
8267             }
8268         }
8269
8270         if (appData.icsActive) {
8271             if (appData.quietPlay &&
8272                 (gameMode == IcsPlayingWhite ||
8273                  gameMode == IcsPlayingBlack)) {
8274                 SendToICS(ics_prefix);
8275                 SendToICS("set shout 1\n");
8276             }
8277             nextGameMode = IcsIdle;
8278             ics_user_moved = FALSE;
8279             /* clean up premove.  It's ugly when the game has ended and the
8280              * premove highlights are still on the board.
8281              */
8282             if (gotPremove) {
8283               gotPremove = FALSE;
8284               ClearPremoveHighlights();
8285               DrawPosition(FALSE, boards[currentMove]);
8286             }
8287             if (whosays == GE_ICS) {
8288                 switch (result) {
8289                 case WhiteWins:
8290                     if (gameMode == IcsPlayingWhite)
8291                         PlayIcsWinSound();
8292                     else if(gameMode == IcsPlayingBlack)
8293                         PlayIcsLossSound();
8294                     break;
8295                 case BlackWins:
8296                     if (gameMode == IcsPlayingBlack)
8297                         PlayIcsWinSound();
8298                     else if(gameMode == IcsPlayingWhite)
8299                         PlayIcsLossSound();
8300                     break;
8301                 case GameIsDrawn:
8302                     PlayIcsDrawSound();
8303                     break;
8304                 default:
8305                     PlayIcsUnfinishedSound();
8306                 }
8307             }
8308         } else if (gameMode == EditGame ||
8309                    gameMode == PlayFromGameFile ||
8310                    gameMode == AnalyzeMode ||
8311                    gameMode == AnalyzeFile) {
8312             nextGameMode = gameMode;
8313         } else {
8314             nextGameMode = EndOfGame;
8315         }
8316         pausing = FALSE;
8317         ModeHighlight();
8318     } else {
8319         nextGameMode = gameMode;
8320     }
8321
8322     if (appData.noChessProgram) {
8323         gameMode = nextGameMode;
8324         ModeHighlight();
8325         endingGame = 0; /* [HGM] crash */
8326         return;
8327     }
8328
8329     if (first.reuse) {
8330         /* Put first chess program into idle state */
8331         if (first.pr != NoProc &&
8332             (gameMode == MachinePlaysWhite ||
8333              gameMode == MachinePlaysBlack ||
8334              gameMode == TwoMachinesPlay ||
8335              gameMode == IcsPlayingWhite ||
8336              gameMode == IcsPlayingBlack ||
8337              gameMode == BeginningOfGame)) {
8338             SendToProgram("force\n", &first);
8339             if (first.usePing) {
8340               char buf[MSG_SIZ];
8341               sprintf(buf, "ping %d\n", ++first.lastPing);
8342               SendToProgram(buf, &first);
8343             }
8344         }
8345     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8346         /* Kill off first chess program */
8347         if (first.isr != NULL)
8348           RemoveInputSource(first.isr);
8349         first.isr = NULL;
8350
8351         if (first.pr != NoProc) {
8352             ExitAnalyzeMode();
8353             DoSleep( appData.delayBeforeQuit );
8354             SendToProgram("quit\n", &first);
8355             DoSleep( appData.delayAfterQuit );
8356             DestroyChildProcess(first.pr, first.useSigterm);
8357         }
8358         first.pr = NoProc;
8359     }
8360     if (second.reuse) {
8361         /* Put second chess program into idle state */
8362         if (second.pr != NoProc &&
8363             gameMode == TwoMachinesPlay) {
8364             SendToProgram("force\n", &second);
8365             if (second.usePing) {
8366               char buf[MSG_SIZ];
8367               sprintf(buf, "ping %d\n", ++second.lastPing);
8368               SendToProgram(buf, &second);
8369             }
8370         }
8371     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8372         /* Kill off second chess program */
8373         if (second.isr != NULL)
8374           RemoveInputSource(second.isr);
8375         second.isr = NULL;
8376
8377         if (second.pr != NoProc) {
8378             DoSleep( appData.delayBeforeQuit );
8379             SendToProgram("quit\n", &second);
8380             DoSleep( appData.delayAfterQuit );
8381             DestroyChildProcess(second.pr, second.useSigterm);
8382         }
8383         second.pr = NoProc;
8384     }
8385
8386     if (matchMode && gameMode == TwoMachinesPlay) {
8387         switch (result) {
8388         case WhiteWins:
8389           if (first.twoMachinesColor[0] == 'w') {
8390             first.matchWins++;
8391           } else {
8392             second.matchWins++;
8393           }
8394           break;
8395         case BlackWins:
8396           if (first.twoMachinesColor[0] == 'b') {
8397             first.matchWins++;
8398           } else {
8399             second.matchWins++;
8400           }
8401           break;
8402         default:
8403           break;
8404         }
8405         if (matchGame < appData.matchGames) {
8406             char *tmp;
8407             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8408                 tmp = first.twoMachinesColor;
8409                 first.twoMachinesColor = second.twoMachinesColor;
8410                 second.twoMachinesColor = tmp;
8411             }
8412             gameMode = nextGameMode;
8413             matchGame++;
8414             if(appData.matchPause>10000 || appData.matchPause<10)
8415                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8416             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8417             endingGame = 0; /* [HGM] crash */
8418             return;
8419         } else {
8420             char buf[MSG_SIZ];
8421             gameMode = nextGameMode;
8422             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8423                     first.tidy, second.tidy,
8424                     first.matchWins, second.matchWins,
8425                     appData.matchGames - (first.matchWins + second.matchWins));
8426             DisplayFatalError(buf, 0, 0);
8427         }
8428     }
8429     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8430         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8431       ExitAnalyzeMode();
8432     gameMode = nextGameMode;
8433     ModeHighlight();
8434     endingGame = 0;  /* [HGM] crash */
8435 }
8436
8437 /* Assumes program was just initialized (initString sent).
8438    Leaves program in force mode. */
8439 void
8440 FeedMovesToProgram(cps, upto)
8441      ChessProgramState *cps;
8442      int upto;
8443 {
8444     int i;
8445
8446     if (appData.debugMode)
8447       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8448               startedFromSetupPosition ? "position and " : "",
8449               backwardMostMove, upto, cps->which);
8450     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8451         // [HGM] variantswitch: make engine aware of new variant
8452         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8453                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8454         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8455         SendToProgram(buf, cps);
8456         currentlyInitializedVariant = gameInfo.variant;
8457     }
8458     SendToProgram("force\n", cps);
8459     if (startedFromSetupPosition) {
8460         SendBoard(cps, backwardMostMove);
8461     if (appData.debugMode) {
8462         fprintf(debugFP, "feedMoves\n");
8463     }
8464     }
8465     for (i = backwardMostMove; i < upto; i++) {
8466         SendMoveToProgram(i, cps);
8467     }
8468 }
8469
8470
8471 void
8472 ResurrectChessProgram()
8473 {
8474      /* The chess program may have exited.
8475         If so, restart it and feed it all the moves made so far. */
8476
8477     if (appData.noChessProgram || first.pr != NoProc) return;
8478
8479     StartChessProgram(&first);
8480     InitChessProgram(&first, FALSE);
8481     FeedMovesToProgram(&first, currentMove);
8482
8483     if (!first.sendTime) {
8484         /* can't tell gnuchess what its clock should read,
8485            so we bow to its notion. */
8486         ResetClocks();
8487         timeRemaining[0][currentMove] = whiteTimeRemaining;
8488         timeRemaining[1][currentMove] = blackTimeRemaining;
8489     }
8490
8491     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8492                 appData.icsEngineAnalyze) && first.analysisSupport) {
8493       SendToProgram("analyze\n", &first);
8494       first.analyzing = TRUE;
8495     }
8496 }
8497
8498 /*
8499  * Button procedures
8500  */
8501 void
8502 Reset(redraw, init)
8503      int redraw, init;
8504 {
8505     int i;
8506
8507     if (appData.debugMode) {
8508         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8509                 redraw, init, gameMode);
8510     }
8511     CleanupTail(); // [HGM] vari: delete any stored variations
8512     pausing = pauseExamInvalid = FALSE;
8513     startedFromSetupPosition = blackPlaysFirst = FALSE;
8514     firstMove = TRUE;
8515     whiteFlag = blackFlag = FALSE;
8516     userOfferedDraw = FALSE;
8517     hintRequested = bookRequested = FALSE;
8518     first.maybeThinking = FALSE;
8519     second.maybeThinking = FALSE;
8520     first.bookSuspend = FALSE; // [HGM] book
8521     second.bookSuspend = FALSE;
8522     thinkOutput[0] = NULLCHAR;
8523     lastHint[0] = NULLCHAR;
8524     ClearGameInfo(&gameInfo);
8525     gameInfo.variant = StringToVariant(appData.variant);
8526     ics_user_moved = ics_clock_paused = FALSE;
8527     ics_getting_history = H_FALSE;
8528     ics_gamenum = -1;
8529     white_holding[0] = black_holding[0] = NULLCHAR;
8530     ClearProgramStats();
8531     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8532
8533     ResetFrontEnd();
8534     ClearHighlights();
8535     flipView = appData.flipView;
8536     ClearPremoveHighlights();
8537     gotPremove = FALSE;
8538     alarmSounded = FALSE;
8539
8540     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8541     if(appData.serverMovesName != NULL) {
8542         /* [HGM] prepare to make moves file for broadcasting */
8543         clock_t t = clock();
8544         if(serverMoves != NULL) fclose(serverMoves);
8545         serverMoves = fopen(appData.serverMovesName, "r");
8546         if(serverMoves != NULL) {
8547             fclose(serverMoves);
8548             /* delay 15 sec before overwriting, so all clients can see end */
8549             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8550         }
8551         serverMoves = fopen(appData.serverMovesName, "w");
8552     }
8553
8554     ExitAnalyzeMode();
8555     gameMode = BeginningOfGame;
8556     ModeHighlight();
8557
8558     if(appData.icsActive) gameInfo.variant = VariantNormal;
8559     currentMove = forwardMostMove = backwardMostMove = 0;
8560     InitPosition(redraw);
8561     for (i = 0; i < MAX_MOVES; i++) {
8562         if (commentList[i] != NULL) {
8563             free(commentList[i]);
8564             commentList[i] = NULL;
8565         }
8566     }
8567
8568     ResetClocks();
8569     timeRemaining[0][0] = whiteTimeRemaining;
8570     timeRemaining[1][0] = blackTimeRemaining;
8571     if (first.pr == NULL) {
8572         StartChessProgram(&first);
8573     }
8574     if (init) {
8575             InitChessProgram(&first, startedFromSetupPosition);
8576     }
8577
8578     DisplayTitle("");
8579     DisplayMessage("", "");
8580     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8581     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8582     return;
8583 }
8584
8585 void
8586 AutoPlayGameLoop()
8587 {
8588     for (;;) {
8589         if (!AutoPlayOneMove())
8590           return;
8591         if (matchMode || appData.timeDelay == 0)
8592           continue;
8593         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8594           return;
8595         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8596         break;
8597     }
8598 }
8599
8600
8601 int
8602 AutoPlayOneMove()
8603 {
8604     int fromX, fromY, toX, toY;
8605
8606     if (appData.debugMode) {
8607       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8608     }
8609
8610     if (gameMode != PlayFromGameFile)
8611       return FALSE;
8612
8613     if (currentMove >= forwardMostMove) {
8614       gameMode = EditGame;
8615       ModeHighlight();
8616
8617       /* [AS] Clear current move marker at the end of a game */
8618       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8619
8620       return FALSE;
8621     }
8622
8623     toX = moveList[currentMove][2] - AAA;
8624     toY = moveList[currentMove][3] - ONE;
8625
8626     if (moveList[currentMove][1] == '@') {
8627         if (appData.highlightLastMove) {
8628             SetHighlights(-1, -1, toX, toY);
8629         }
8630     } else {
8631         fromX = moveList[currentMove][0] - AAA;
8632         fromY = moveList[currentMove][1] - ONE;
8633
8634         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8635
8636         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8637
8638         if (appData.highlightLastMove) {
8639             SetHighlights(fromX, fromY, toX, toY);
8640         }
8641     }
8642     DisplayMove(currentMove);
8643     SendMoveToProgram(currentMove++, &first);
8644     DisplayBothClocks();
8645     DrawPosition(FALSE, boards[currentMove]);
8646     // [HGM] PV info: always display, routine tests if empty
8647     DisplayComment(currentMove - 1, commentList[currentMove]);
8648     return TRUE;
8649 }
8650
8651
8652 int
8653 LoadGameOneMove(readAhead)
8654      ChessMove readAhead;
8655 {
8656     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8657     char promoChar = NULLCHAR;
8658     ChessMove moveType;
8659     char move[MSG_SIZ];
8660     char *p, *q;
8661
8662     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8663         gameMode != AnalyzeMode && gameMode != Training) {
8664         gameFileFP = NULL;
8665         return FALSE;
8666     }
8667
8668     yyboardindex = forwardMostMove;
8669     if (readAhead != (ChessMove)0) {
8670       moveType = readAhead;
8671     } else {
8672       if (gameFileFP == NULL)
8673           return FALSE;
8674       moveType = (ChessMove) yylex();
8675     }
8676
8677     done = FALSE;
8678     switch (moveType) {
8679       case Comment:
8680         if (appData.debugMode)
8681           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8682         p = yy_text;
8683
8684         /* append the comment but don't display it */
8685         AppendComment(currentMove, p, FALSE);
8686         return TRUE;
8687
8688       case WhiteCapturesEnPassant:
8689       case BlackCapturesEnPassant:
8690       case WhitePromotionChancellor:
8691       case BlackPromotionChancellor:
8692       case WhitePromotionArchbishop:
8693       case BlackPromotionArchbishop:
8694       case WhitePromotionCentaur:
8695       case BlackPromotionCentaur:
8696       case WhitePromotionQueen:
8697       case BlackPromotionQueen:
8698       case WhitePromotionRook:
8699       case BlackPromotionRook:
8700       case WhitePromotionBishop:
8701       case BlackPromotionBishop:
8702       case WhitePromotionKnight:
8703       case BlackPromotionKnight:
8704       case WhitePromotionKing:
8705       case BlackPromotionKing:
8706       case NormalMove:
8707       case WhiteKingSideCastle:
8708       case WhiteQueenSideCastle:
8709       case BlackKingSideCastle:
8710       case BlackQueenSideCastle:
8711       case WhiteKingSideCastleWild:
8712       case WhiteQueenSideCastleWild:
8713       case BlackKingSideCastleWild:
8714       case BlackQueenSideCastleWild:
8715       /* PUSH Fabien */
8716       case WhiteHSideCastleFR:
8717       case WhiteASideCastleFR:
8718       case BlackHSideCastleFR:
8719       case BlackASideCastleFR:
8720       /* POP Fabien */
8721         if (appData.debugMode)
8722           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8723         fromX = currentMoveString[0] - AAA;
8724         fromY = currentMoveString[1] - ONE;
8725         toX = currentMoveString[2] - AAA;
8726         toY = currentMoveString[3] - ONE;
8727         promoChar = currentMoveString[4];
8728         break;
8729
8730       case WhiteDrop:
8731       case BlackDrop:
8732         if (appData.debugMode)
8733           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8734         fromX = moveType == WhiteDrop ?
8735           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8736         (int) CharToPiece(ToLower(currentMoveString[0]));
8737         fromY = DROP_RANK;
8738         toX = currentMoveString[2] - AAA;
8739         toY = currentMoveString[3] - ONE;
8740         break;
8741
8742       case WhiteWins:
8743       case BlackWins:
8744       case GameIsDrawn:
8745       case GameUnfinished:
8746         if (appData.debugMode)
8747           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8748         p = strchr(yy_text, '{');
8749         if (p == NULL) p = strchr(yy_text, '(');
8750         if (p == NULL) {
8751             p = yy_text;
8752             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8753         } else {
8754             q = strchr(p, *p == '{' ? '}' : ')');
8755             if (q != NULL) *q = NULLCHAR;
8756             p++;
8757         }
8758         GameEnds(moveType, p, GE_FILE);
8759         done = TRUE;
8760         if (cmailMsgLoaded) {
8761             ClearHighlights();
8762             flipView = WhiteOnMove(currentMove);
8763             if (moveType == GameUnfinished) flipView = !flipView;
8764             if (appData.debugMode)
8765               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8766         }
8767         break;
8768
8769       case (ChessMove) 0:       /* end of file */
8770         if (appData.debugMode)
8771           fprintf(debugFP, "Parser hit end of file\n");
8772         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8773           case MT_NONE:
8774           case MT_CHECK:
8775             break;
8776           case MT_CHECKMATE:
8777           case MT_STAINMATE:
8778             if (WhiteOnMove(currentMove)) {
8779                 GameEnds(BlackWins, "Black mates", GE_FILE);
8780             } else {
8781                 GameEnds(WhiteWins, "White mates", GE_FILE);
8782             }
8783             break;
8784           case MT_STALEMATE:
8785             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8786             break;
8787         }
8788         done = TRUE;
8789         break;
8790
8791       case MoveNumberOne:
8792         if (lastLoadGameStart == GNUChessGame) {
8793             /* GNUChessGames have numbers, but they aren't move numbers */
8794             if (appData.debugMode)
8795               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8796                       yy_text, (int) moveType);
8797             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8798         }
8799         /* else fall thru */
8800
8801       case XBoardGame:
8802       case GNUChessGame:
8803       case PGNTag:
8804         /* Reached start of next game in file */
8805         if (appData.debugMode)
8806           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8807         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8808           case MT_NONE:
8809           case MT_CHECK:
8810             break;
8811           case MT_CHECKMATE:
8812           case MT_STAINMATE:
8813             if (WhiteOnMove(currentMove)) {
8814                 GameEnds(BlackWins, "Black mates", GE_FILE);
8815             } else {
8816                 GameEnds(WhiteWins, "White mates", GE_FILE);
8817             }
8818             break;
8819           case MT_STALEMATE:
8820             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8821             break;
8822         }
8823         done = TRUE;
8824         break;
8825
8826       case PositionDiagram:     /* should not happen; ignore */
8827       case ElapsedTime:         /* ignore */
8828       case NAG:                 /* ignore */
8829         if (appData.debugMode)
8830           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8831                   yy_text, (int) moveType);
8832         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8833
8834       case IllegalMove:
8835         if (appData.testLegality) {
8836             if (appData.debugMode)
8837               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8838             sprintf(move, _("Illegal move: %d.%s%s"),
8839                     (forwardMostMove / 2) + 1,
8840                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8841             DisplayError(move, 0);
8842             done = TRUE;
8843         } else {
8844             if (appData.debugMode)
8845               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8846                       yy_text, currentMoveString);
8847             fromX = currentMoveString[0] - AAA;
8848             fromY = currentMoveString[1] - ONE;
8849             toX = currentMoveString[2] - AAA;
8850             toY = currentMoveString[3] - ONE;
8851             promoChar = currentMoveString[4];
8852         }
8853         break;
8854
8855       case AmbiguousMove:
8856         if (appData.debugMode)
8857           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8858         sprintf(move, _("Ambiguous move: %d.%s%s"),
8859                 (forwardMostMove / 2) + 1,
8860                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8861         DisplayError(move, 0);
8862         done = TRUE;
8863         break;
8864
8865       default:
8866       case ImpossibleMove:
8867         if (appData.debugMode)
8868           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8869         sprintf(move, _("Illegal move: %d.%s%s"),
8870                 (forwardMostMove / 2) + 1,
8871                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8872         DisplayError(move, 0);
8873         done = TRUE;
8874         break;
8875     }
8876
8877     if (done) {
8878         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8879             DrawPosition(FALSE, boards[currentMove]);
8880             DisplayBothClocks();
8881             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8882               DisplayComment(currentMove - 1, commentList[currentMove]);
8883         }
8884         (void) StopLoadGameTimer();
8885         gameFileFP = NULL;
8886         cmailOldMove = forwardMostMove;
8887         return FALSE;
8888     } else {
8889         /* currentMoveString is set as a side-effect of yylex */
8890         strcat(currentMoveString, "\n");
8891         strcpy(moveList[forwardMostMove], currentMoveString);
8892
8893         thinkOutput[0] = NULLCHAR;
8894         MakeMove(fromX, fromY, toX, toY, promoChar);
8895         currentMove = forwardMostMove;
8896         return TRUE;
8897     }
8898 }
8899
8900 /* Load the nth game from the given file */
8901 int
8902 LoadGameFromFile(filename, n, title, useList)
8903      char *filename;
8904      int n;
8905      char *title;
8906      /*Boolean*/ int useList;
8907 {
8908     FILE *f;
8909     char buf[MSG_SIZ];
8910
8911     if (strcmp(filename, "-") == 0) {
8912         f = stdin;
8913         title = "stdin";
8914     } else {
8915         f = fopen(filename, "rb");
8916         if (f == NULL) {
8917           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8918             DisplayError(buf, errno);
8919             return FALSE;
8920         }
8921     }
8922     if (fseek(f, 0, 0) == -1) {
8923         /* f is not seekable; probably a pipe */
8924         useList = FALSE;
8925     }
8926     if (useList && n == 0) {
8927         int error = GameListBuild(f);
8928         if (error) {
8929             DisplayError(_("Cannot build game list"), error);
8930         } else if (!ListEmpty(&gameList) &&
8931                    ((ListGame *) gameList.tailPred)->number > 1) {
8932           // TODO convert to GTK
8933           //        GameListPopUp(f, title);
8934             return TRUE;
8935         }
8936         GameListDestroy();
8937         n = 1;
8938     }
8939     if (n == 0) n = 1;
8940     return LoadGame(f, n, title, FALSE);
8941 }
8942
8943
8944 void
8945 MakeRegisteredMove()
8946 {
8947     int fromX, fromY, toX, toY;
8948     char promoChar;
8949     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8950         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8951           case CMAIL_MOVE:
8952           case CMAIL_DRAW:
8953             if (appData.debugMode)
8954               fprintf(debugFP, "Restoring %s for game %d\n",
8955                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8956
8957             thinkOutput[0] = NULLCHAR;
8958             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8959             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8960             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8961             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8962             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8963             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8964             MakeMove(fromX, fromY, toX, toY, promoChar);
8965             ShowMove(fromX, fromY, toX, toY);
8966             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8967               case MT_NONE:
8968               case MT_CHECK:
8969                 break;
8970
8971               case MT_CHECKMATE:
8972               case MT_STAINMATE:
8973                 if (WhiteOnMove(currentMove)) {
8974                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8975                 } else {
8976                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8977                 }
8978                 break;
8979
8980               case MT_STALEMATE:
8981                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8982                 break;
8983             }
8984
8985             break;
8986
8987           case CMAIL_RESIGN:
8988             if (WhiteOnMove(currentMove)) {
8989                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8990             } else {
8991                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8992             }
8993             break;
8994
8995           case CMAIL_ACCEPT:
8996             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8997             break;
8998
8999           default:
9000             break;
9001         }
9002     }
9003
9004     return;
9005 }
9006
9007 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9008 int
9009 CmailLoadGame(f, gameNumber, title, useList)
9010      FILE *f;
9011      int gameNumber;
9012      char *title;
9013      int useList;
9014 {
9015     int retVal;
9016
9017     if (gameNumber > nCmailGames) {
9018         DisplayError(_("No more games in this message"), 0);
9019         return FALSE;
9020     }
9021     if (f == lastLoadGameFP) {
9022         int offset = gameNumber - lastLoadGameNumber;
9023         if (offset == 0) {
9024             cmailMsg[0] = NULLCHAR;
9025             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9026                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9027                 nCmailMovesRegistered--;
9028             }
9029             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9030             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9031                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9032             }
9033         } else {
9034             if (! RegisterMove()) return FALSE;
9035         }
9036     }
9037
9038     retVal = LoadGame(f, gameNumber, title, useList);
9039
9040     /* Make move registered during previous look at this game, if any */
9041     MakeRegisteredMove();
9042
9043     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9044         commentList[currentMove]
9045           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9046         DisplayComment(currentMove - 1, commentList[currentMove]);
9047     }
9048
9049     return retVal;
9050 }
9051
9052 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9053 int
9054 ReloadGame(offset)
9055      int offset;
9056 {
9057     int gameNumber = lastLoadGameNumber + offset;
9058     if (lastLoadGameFP == NULL) {
9059         DisplayError(_("No game has been loaded yet"), 0);
9060         return FALSE;
9061     }
9062     if (gameNumber <= 0) {
9063         DisplayError(_("Can't back up any further"), 0);
9064         return FALSE;
9065     }
9066     if (cmailMsgLoaded) {
9067         return CmailLoadGame(lastLoadGameFP, gameNumber,
9068                              lastLoadGameTitle, lastLoadGameUseList);
9069     } else {
9070         return LoadGame(lastLoadGameFP, gameNumber,
9071                         lastLoadGameTitle, lastLoadGameUseList);
9072     }
9073 }
9074
9075
9076
9077 /* Load the nth game from open file f */
9078 int
9079 LoadGame(f, gameNumber, title, useList)
9080      FILE *f;
9081      int gameNumber;
9082      char *title;
9083      int useList;
9084 {
9085     ChessMove cm;
9086     char buf[MSG_SIZ];
9087     int gn = gameNumber;
9088     ListGame *lg = NULL;
9089     int numPGNTags = 0;
9090     int err;
9091     GameMode oldGameMode;
9092     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9093
9094     if (appData.debugMode)
9095         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9096
9097     if (gameMode == Training )
9098         SetTrainingModeOff();
9099
9100     oldGameMode = gameMode;
9101     if (gameMode != BeginningOfGame) 
9102       {
9103         Reset(FALSE, TRUE);
9104       };
9105
9106     gameFileFP = f;
9107     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9108       {
9109         fclose(lastLoadGameFP);
9110       };
9111
9112     if (useList) 
9113       {
9114         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9115         
9116         if (lg) 
9117           {
9118             fseek(f, lg->offset, 0);
9119             GameListHighlight(gameNumber);
9120             gn = 1;
9121           }
9122         else 
9123           {
9124             DisplayError(_("Game number out of range"), 0);
9125             return FALSE;
9126           };
9127       } 
9128     else 
9129       {
9130         GameListDestroy();
9131         if (fseek(f, 0, 0) == -1) 
9132           {
9133             if (f == lastLoadGameFP ?
9134                 gameNumber == lastLoadGameNumber + 1 :
9135                 gameNumber == 1) 
9136               {
9137                 gn = 1;
9138               } 
9139             else 
9140               {
9141                 DisplayError(_("Can't seek on game file"), 0);
9142                 return FALSE;
9143               };
9144           };
9145       };
9146
9147     lastLoadGameFP      = f;
9148     lastLoadGameNumber  = gameNumber;
9149     strcpy(lastLoadGameTitle, title);
9150     lastLoadGameUseList = useList;
9151
9152     yynewfile(f);
9153
9154     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9155       {
9156         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9157                  lg->gameInfo.black);
9158         DisplayTitle(buf);
9159       } 
9160     else if (*title != NULLCHAR) 
9161       {
9162         if (gameNumber > 1) 
9163           {
9164             sprintf(buf, "%s %d", title, gameNumber);
9165             DisplayTitle(buf);
9166           } 
9167         else 
9168           {
9169             DisplayTitle(title);
9170           };
9171       };
9172
9173     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9174       {
9175         gameMode = PlayFromGameFile;
9176         ModeHighlight();
9177       };
9178
9179     currentMove = forwardMostMove = backwardMostMove = 0;
9180     CopyBoard(boards[0], initialPosition);
9181     StopClocks();
9182
9183     /*
9184      * Skip the first gn-1 games in the file.
9185      * Also skip over anything that precedes an identifiable
9186      * start of game marker, to avoid being confused by
9187      * garbage at the start of the file.  Currently
9188      * recognized start of game markers are the move number "1",
9189      * the pattern "gnuchess .* game", the pattern
9190      * "^[#;%] [^ ]* game file", and a PGN tag block.
9191      * A game that starts with one of the latter two patterns
9192      * will also have a move number 1, possibly
9193      * following a position diagram.
9194      * 5-4-02: Let's try being more lenient and allowing a game to
9195      * start with an unnumbered move.  Does that break anything?
9196      */
9197     cm = lastLoadGameStart = (ChessMove) 0;
9198     while (gn > 0) {
9199         yyboardindex = forwardMostMove;
9200         cm = (ChessMove) yylex();
9201         switch (cm) {
9202           case (ChessMove) 0:
9203             if (cmailMsgLoaded) {
9204                 nCmailGames = CMAIL_MAX_GAMES - gn;
9205             } else {
9206                 Reset(TRUE, TRUE);
9207                 DisplayError(_("Game not found in file"), 0);
9208             }
9209             return FALSE;
9210
9211           case GNUChessGame:
9212           case XBoardGame:
9213             gn--;
9214             lastLoadGameStart = cm;
9215             break;
9216
9217           case MoveNumberOne:
9218             switch (lastLoadGameStart) {
9219               case GNUChessGame:
9220               case XBoardGame:
9221               case PGNTag:
9222                 break;
9223               case MoveNumberOne:
9224               case (ChessMove) 0:
9225                 gn--;           /* count this game */
9226                 lastLoadGameStart = cm;
9227                 break;
9228               default:
9229                 /* impossible */
9230                 break;
9231             }
9232             break;
9233
9234           case PGNTag:
9235             switch (lastLoadGameStart) {
9236               case GNUChessGame:
9237               case PGNTag:
9238               case MoveNumberOne:
9239               case (ChessMove) 0:
9240                 gn--;           /* count this game */
9241                 lastLoadGameStart = cm;
9242                 break;
9243               case XBoardGame:
9244                 lastLoadGameStart = cm; /* game counted already */
9245                 break;
9246               default:
9247                 /* impossible */
9248                 break;
9249             }
9250             if (gn > 0) {
9251                 do {
9252                     yyboardindex = forwardMostMove;
9253                     cm = (ChessMove) yylex();
9254                 } while (cm == PGNTag || cm == Comment);
9255             }
9256             break;
9257
9258           case WhiteWins:
9259           case BlackWins:
9260           case GameIsDrawn:
9261             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9262                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9263                     != CMAIL_OLD_RESULT) {
9264                     nCmailResults ++ ;
9265                     cmailResult[  CMAIL_MAX_GAMES
9266                                 - gn - 1] = CMAIL_OLD_RESULT;
9267                 }
9268             }
9269             break;
9270
9271           case NormalMove:
9272             /* Only a NormalMove can be at the start of a game
9273              * without a position diagram. */
9274             if (lastLoadGameStart == (ChessMove) 0) {
9275               gn--;
9276               lastLoadGameStart = MoveNumberOne;
9277             }
9278             break;
9279
9280           default:
9281             break;
9282         }
9283     }
9284
9285     if (appData.debugMode)
9286       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9287
9288     if (cm == XBoardGame) {
9289         /* Skip any header junk before position diagram and/or move 1 */
9290         for (;;) {
9291             yyboardindex = forwardMostMove;
9292             cm = (ChessMove) yylex();
9293
9294             if (cm == (ChessMove) 0 ||
9295                 cm == GNUChessGame || cm == XBoardGame) {
9296                 /* Empty game; pretend end-of-file and handle later */
9297                 cm = (ChessMove) 0;
9298                 break;
9299             }
9300
9301             if (cm == MoveNumberOne || cm == PositionDiagram ||
9302                 cm == PGNTag || cm == Comment)
9303               break;
9304         }
9305     } else if (cm == GNUChessGame) {
9306         if (gameInfo.event != NULL) {
9307             free(gameInfo.event);
9308         }
9309         gameInfo.event = StrSave(yy_text);
9310     }
9311
9312     startedFromSetupPosition = FALSE;
9313     while (cm == PGNTag) {
9314         if (appData.debugMode)
9315           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9316         err = ParsePGNTag(yy_text, &gameInfo);
9317         if (!err) numPGNTags++;
9318
9319         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9320         if(gameInfo.variant != oldVariant) {
9321             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9322             InitPosition(TRUE);
9323             oldVariant = gameInfo.variant;
9324             if (appData.debugMode)
9325               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9326         }
9327
9328
9329         if (gameInfo.fen != NULL) {
9330           Board initial_position;
9331           startedFromSetupPosition = TRUE;
9332           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9333             Reset(TRUE, TRUE);
9334             DisplayError(_("Bad FEN position in file"), 0);
9335             return FALSE;
9336           }
9337           CopyBoard(boards[0], initial_position);
9338           if (blackPlaysFirst) {
9339             currentMove = forwardMostMove = backwardMostMove = 1;
9340             CopyBoard(boards[1], initial_position);
9341             strcpy(moveList[0], "");
9342             strcpy(parseList[0], "");
9343             timeRemaining[0][1] = whiteTimeRemaining;
9344             timeRemaining[1][1] = blackTimeRemaining;
9345             if (commentList[0] != NULL) {
9346               commentList[1] = commentList[0];
9347               commentList[0] = NULL;
9348             }
9349           } else {
9350             currentMove = forwardMostMove = backwardMostMove = 0;
9351           }
9352           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9353           {   int i;
9354               initialRulePlies = FENrulePlies;
9355               for( i=0; i< nrCastlingRights; i++ )
9356                   initialRights[i] = initial_position[CASTLING][i];
9357           }
9358           yyboardindex = forwardMostMove;
9359           free(gameInfo.fen);
9360           gameInfo.fen = NULL;
9361         }
9362
9363         yyboardindex = forwardMostMove;
9364         cm = (ChessMove) yylex();
9365
9366         /* Handle comments interspersed among the tags */
9367         while (cm == Comment) {
9368             char *p;
9369             if (appData.debugMode)
9370               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9371             p = yy_text;
9372             AppendComment(currentMove, p, FALSE);
9373             yyboardindex = forwardMostMove;
9374             cm = (ChessMove) yylex();
9375         }
9376     }
9377
9378     /* don't rely on existence of Event tag since if game was
9379      * pasted from clipboard the Event tag may not exist
9380      */
9381     if (numPGNTags > 0){
9382         char *tags;
9383         if (gameInfo.variant == VariantNormal) {
9384           gameInfo.variant = StringToVariant(gameInfo.event);
9385         }
9386         if (!matchMode) {
9387           if( appData.autoDisplayTags ) {
9388             tags = PGNTags(&gameInfo);
9389             TagsPopUp(tags, CmailMsg());
9390             free(tags);
9391           }
9392         }
9393     } else {
9394         /* Make something up, but don't display it now */
9395         SetGameInfo();
9396         TagsPopDown();
9397     }
9398
9399     if (cm == PositionDiagram) {
9400         int i, j;
9401         char *p;
9402         Board initial_position;
9403
9404         if (appData.debugMode)
9405           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9406
9407         if (!startedFromSetupPosition) {
9408             p = yy_text;
9409             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9410               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9411                 switch (*p) {
9412                   case '[':
9413                   case '-':
9414                   case ' ':
9415                   case '\t':
9416                   case '\n':
9417                   case '\r':
9418                     break;
9419                   default:
9420                     initial_position[i][j++] = CharToPiece(*p);
9421                     break;
9422                 }
9423             while (*p == ' ' || *p == '\t' ||
9424                    *p == '\n' || *p == '\r') p++;
9425
9426             if (strncmp(p, "black", strlen("black"))==0)
9427               blackPlaysFirst = TRUE;
9428             else
9429               blackPlaysFirst = FALSE;
9430             startedFromSetupPosition = TRUE;
9431
9432             CopyBoard(boards[0], initial_position);
9433             if (blackPlaysFirst) {
9434                 currentMove = forwardMostMove = backwardMostMove = 1;
9435                 CopyBoard(boards[1], initial_position);
9436                 strcpy(moveList[0], "");
9437                 strcpy(parseList[0], "");
9438                 timeRemaining[0][1] = whiteTimeRemaining;
9439                 timeRemaining[1][1] = blackTimeRemaining;
9440                 if (commentList[0] != NULL) {
9441                     commentList[1] = commentList[0];
9442                     commentList[0] = NULL;
9443                 }
9444             } else {
9445                 currentMove = forwardMostMove = backwardMostMove = 0;
9446             }
9447         }
9448         yyboardindex = forwardMostMove;
9449         cm = (ChessMove) yylex();
9450     }
9451
9452     if (first.pr == NoProc) {
9453         StartChessProgram(&first);
9454     }
9455     InitChessProgram(&first, FALSE);
9456     SendToProgram("force\n", &first);
9457     if (startedFromSetupPosition) {
9458         SendBoard(&first, forwardMostMove);
9459     if (appData.debugMode) {
9460         fprintf(debugFP, "Load Game\n");
9461     }
9462         DisplayBothClocks();
9463     }
9464
9465     /* [HGM] server: flag to write setup moves in broadcast file as one */
9466     loadFlag = appData.suppressLoadMoves;
9467
9468     while (cm == Comment) {
9469         char *p;
9470         if (appData.debugMode)
9471           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9472         p = yy_text;
9473         AppendComment(currentMove, p, FALSE);
9474         yyboardindex = forwardMostMove;
9475         cm = (ChessMove) yylex();
9476     }
9477
9478     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9479         cm == WhiteWins || cm == BlackWins ||
9480         cm == GameIsDrawn || cm == GameUnfinished) {
9481         DisplayMessage("", _("No moves in game"));
9482         if (cmailMsgLoaded) {
9483             if (appData.debugMode)
9484               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9485             ClearHighlights();
9486             flipView = FALSE;
9487         }
9488         DrawPosition(FALSE, boards[currentMove]);
9489         DisplayBothClocks();
9490         gameMode = EditGame;
9491         ModeHighlight();
9492         gameFileFP = NULL;
9493         cmailOldMove = 0;
9494         return TRUE;
9495     }
9496
9497     // [HGM] PV info: routine tests if comment empty
9498     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9499         DisplayComment(currentMove - 1, commentList[currentMove]);
9500     }
9501     if (!matchMode && appData.timeDelay != 0)
9502       DrawPosition(FALSE, boards[currentMove]);
9503
9504     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9505       programStats.ok_to_send = 1;
9506     }
9507
9508     /* if the first token after the PGN tags is a move
9509      * and not move number 1, retrieve it from the parser
9510      */
9511     if (cm != MoveNumberOne)
9512         LoadGameOneMove(cm);
9513
9514     /* load the remaining moves from the file */
9515     while (LoadGameOneMove((ChessMove)0)) {
9516       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9517       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9518     }
9519
9520     /* rewind to the start of the game */
9521     currentMove = backwardMostMove;
9522
9523     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9524
9525     if (oldGameMode == AnalyzeFile ||
9526         oldGameMode == AnalyzeMode) {
9527       AnalyzeFileEvent();
9528     }
9529
9530     if (matchMode || appData.timeDelay == 0) {
9531       ToEndEvent();
9532       gameMode = EditGame;
9533       ModeHighlight();
9534     } else if (appData.timeDelay > 0) {
9535       AutoPlayGameLoop();
9536     }
9537
9538     if (appData.debugMode)
9539         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9540
9541     loadFlag = 0; /* [HGM] true game starts */
9542     return TRUE;
9543 }
9544
9545 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9546 int
9547 ReloadPosition(offset)
9548      int offset;
9549 {
9550     int positionNumber = lastLoadPositionNumber + offset;
9551     if (lastLoadPositionFP == NULL) {
9552         DisplayError(_("No position has been loaded yet"), 0);
9553         return FALSE;
9554     }
9555     if (positionNumber <= 0) {
9556         DisplayError(_("Can't back up any further"), 0);
9557         return FALSE;
9558     }
9559     return LoadPosition(lastLoadPositionFP, positionNumber,
9560                         lastLoadPositionTitle);
9561 }
9562
9563 /* Load the nth position from the given file */
9564 int
9565 LoadPositionFromFile(filename, n, title)
9566      char *filename;
9567      int n;
9568      char *title;
9569 {
9570     FILE *f;
9571     char buf[MSG_SIZ];
9572
9573     if (strcmp(filename, "-") == 0) {
9574         return LoadPosition(stdin, n, "stdin");
9575     } else {
9576         f = fopen(filename, "rb");
9577         if (f == NULL) {
9578             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9579             DisplayError(buf, errno);
9580             return FALSE;
9581         } else {
9582             return LoadPosition(f, n, title);
9583         }
9584     }
9585 }
9586
9587 /* Load the nth position from the given open file, and close it */
9588 int
9589 LoadPosition(f, positionNumber, title)
9590      FILE *f;
9591      int positionNumber;
9592      char *title;
9593 {
9594     char *p, line[MSG_SIZ];
9595     Board initial_position;
9596     int i, j, fenMode, pn;
9597
9598     if (gameMode == Training )
9599         SetTrainingModeOff();
9600
9601     if (gameMode != BeginningOfGame) {
9602         Reset(FALSE, TRUE);
9603     }
9604     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9605         fclose(lastLoadPositionFP);
9606     }
9607     if (positionNumber == 0) positionNumber = 1;
9608     lastLoadPositionFP = f;
9609     lastLoadPositionNumber = positionNumber;
9610     strcpy(lastLoadPositionTitle, title);
9611     if (first.pr == NoProc) {
9612       StartChessProgram(&first);
9613       InitChessProgram(&first, FALSE);
9614     }
9615     pn = positionNumber;
9616     if (positionNumber < 0) {
9617         /* Negative position number means to seek to that byte offset */
9618         if (fseek(f, -positionNumber, 0) == -1) {
9619             DisplayError(_("Can't seek on position file"), 0);
9620             return FALSE;
9621         };
9622         pn = 1;
9623     } else {
9624         if (fseek(f, 0, 0) == -1) {
9625             if (f == lastLoadPositionFP ?
9626                 positionNumber == lastLoadPositionNumber + 1 :
9627                 positionNumber == 1) {
9628                 pn = 1;
9629             } else {
9630                 DisplayError(_("Can't seek on position file"), 0);
9631                 return FALSE;
9632             }
9633         }
9634     }
9635     /* See if this file is FEN or old-style xboard */
9636     if (fgets(line, MSG_SIZ, f) == NULL) {
9637         DisplayError(_("Position not found in file"), 0);
9638         return FALSE;
9639     }
9640     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9641     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9642
9643     if (pn >= 2) {
9644         if (fenMode || line[0] == '#') pn--;
9645         while (pn > 0) {
9646             /* skip positions before number pn */
9647             if (fgets(line, MSG_SIZ, f) == NULL) {
9648                 Reset(TRUE, TRUE);
9649                 DisplayError(_("Position not found in file"), 0);
9650                 return FALSE;
9651             }
9652             if (fenMode || line[0] == '#') pn--;
9653         }
9654     }
9655
9656     if (fenMode) {
9657         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9658             DisplayError(_("Bad FEN position in file"), 0);
9659             return FALSE;
9660         }
9661     } else {
9662         (void) fgets(line, MSG_SIZ, f);
9663         (void) fgets(line, MSG_SIZ, f);
9664
9665         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9666             (void) fgets(line, MSG_SIZ, f);
9667             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9668                 if (*p == ' ')
9669                   continue;
9670                 initial_position[i][j++] = CharToPiece(*p);
9671             }
9672         }
9673
9674         blackPlaysFirst = FALSE;
9675         if (!feof(f)) {
9676             (void) fgets(line, MSG_SIZ, f);
9677             if (strncmp(line, "black", strlen("black"))==0)
9678               blackPlaysFirst = TRUE;
9679         }
9680     }
9681     startedFromSetupPosition = TRUE;
9682
9683     SendToProgram("force\n", &first);
9684     CopyBoard(boards[0], initial_position);
9685     if (blackPlaysFirst) {
9686         currentMove = forwardMostMove = backwardMostMove = 1;
9687         strcpy(moveList[0], "");
9688         strcpy(parseList[0], "");
9689         CopyBoard(boards[1], initial_position);
9690         DisplayMessage("", _("Black to play"));
9691     } else {
9692         currentMove = forwardMostMove = backwardMostMove = 0;
9693         DisplayMessage("", _("White to play"));
9694     }
9695     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9696     SendBoard(&first, forwardMostMove);
9697     if (appData.debugMode) {
9698 int i, j;
9699   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9700   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9701         fprintf(debugFP, "Load Position\n");
9702     }
9703
9704     if (positionNumber > 1) {
9705         sprintf(line, "%s %d", title, positionNumber);
9706         DisplayTitle(line);
9707     } else {
9708         DisplayTitle(title);
9709     }
9710     gameMode = EditGame;
9711     ModeHighlight();
9712     ResetClocks();
9713     timeRemaining[0][1] = whiteTimeRemaining;
9714     timeRemaining[1][1] = blackTimeRemaining;
9715     DrawPosition(FALSE, boards[currentMove]);
9716
9717     return TRUE;
9718 }
9719
9720
9721 void
9722 CopyPlayerNameIntoFileName(dest, src)
9723      char **dest, *src;
9724 {
9725     while (*src != NULLCHAR && *src != ',') {
9726         if (*src == ' ') {
9727             *(*dest)++ = '_';
9728             src++;
9729         } else {
9730             *(*dest)++ = *src++;
9731         }
9732     }
9733 }
9734
9735 char *DefaultFileName(ext)
9736      char *ext;
9737 {
9738     static char def[MSG_SIZ];
9739     char *p;
9740
9741     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9742         p = def;
9743         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9744         *p++ = '-';
9745         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9746         *p++ = '.';
9747         strcpy(p, ext);
9748     } else {
9749         def[0] = NULLCHAR;
9750     }
9751     return def;
9752 }
9753
9754 /* Save the current game to the given file */
9755 int
9756 SaveGameToFile(filename, append)
9757      char *filename;
9758      int append;
9759 {
9760     FILE *f;
9761     char buf[MSG_SIZ];
9762
9763     if (strcmp(filename, "-") == 0) {
9764         return SaveGame(stdout, 0, NULL);
9765     } else {
9766         f = fopen(filename, append ? "a" : "w");
9767         if (f == NULL) {
9768             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9769             DisplayError(buf, errno);
9770             return FALSE;
9771         } else {
9772             return SaveGame(f, 0, NULL);
9773         }
9774     }
9775 }
9776
9777 char *
9778 SavePart(str)
9779      char *str;
9780 {
9781     static char buf[MSG_SIZ];
9782     char *p;
9783
9784     p = strchr(str, ' ');
9785     if (p == NULL) return str;
9786     strncpy(buf, str, p - str);
9787     buf[p - str] = NULLCHAR;
9788     return buf;
9789 }
9790
9791 #define PGN_MAX_LINE 75
9792
9793 #define PGN_SIDE_WHITE  0
9794 #define PGN_SIDE_BLACK  1
9795
9796 /* [AS] */
9797 static int FindFirstMoveOutOfBook( int side )
9798 {
9799     int result = -1;
9800
9801     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9802         int index = backwardMostMove;
9803         int has_book_hit = 0;
9804
9805         if( (index % 2) != side ) {
9806             index++;
9807         }
9808
9809         while( index < forwardMostMove ) {
9810             /* Check to see if engine is in book */
9811             int depth = pvInfoList[index].depth;
9812             int score = pvInfoList[index].score;
9813             int in_book = 0;
9814
9815             if( depth <= 2 ) {
9816                 in_book = 1;
9817             }
9818             else if( score == 0 && depth == 63 ) {
9819                 in_book = 1; /* Zappa */
9820             }
9821             else if( score == 2 && depth == 99 ) {
9822                 in_book = 1; /* Abrok */
9823             }
9824
9825             has_book_hit += in_book;
9826
9827             if( ! in_book ) {
9828                 result = index;
9829
9830                 break;
9831             }
9832
9833             index += 2;
9834         }
9835     }
9836
9837     return result;
9838 }
9839
9840 /* [AS] */
9841 void GetOutOfBookInfo( char * buf )
9842 {
9843     int oob[2];
9844     int i;
9845     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9846
9847     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9848     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9849
9850     *buf = '\0';
9851
9852     if( oob[0] >= 0 || oob[1] >= 0 ) {
9853         for( i=0; i<2; i++ ) {
9854             int idx = oob[i];
9855
9856             if( idx >= 0 ) {
9857                 if( i > 0 && oob[0] >= 0 ) {
9858                     strcat( buf, "   " );
9859                 }
9860
9861                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9862                 sprintf( buf+strlen(buf), "%s%.2f",
9863                     pvInfoList[idx].score >= 0 ? "+" : "",
9864                     pvInfoList[idx].score / 100.0 );
9865             }
9866         }
9867     }
9868 }
9869
9870 /* Save game in PGN style and close the file */
9871 int
9872 SaveGamePGN(f)
9873      FILE *f;
9874 {
9875     int i, offset, linelen, newblock;
9876     time_t tm;
9877 //    char *movetext;
9878     char numtext[32];
9879     int movelen, numlen, blank;
9880     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9881
9882     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9883
9884     tm = time((time_t *) NULL);
9885
9886     PrintPGNTags(f, &gameInfo);
9887
9888     if (backwardMostMove > 0 || startedFromSetupPosition) {
9889         char *fen = PositionToFEN(backwardMostMove, NULL);
9890         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9891         fprintf(f, "\n{--------------\n");
9892         PrintPosition(f, backwardMostMove);
9893         fprintf(f, "--------------}\n");
9894         free(fen);
9895     }
9896     else {
9897         /* [AS] Out of book annotation */
9898         if( appData.saveOutOfBookInfo ) {
9899             char buf[64];
9900
9901             GetOutOfBookInfo( buf );
9902
9903             if( buf[0] != '\0' ) {
9904                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9905             }
9906         }
9907
9908         fprintf(f, "\n");
9909     }
9910
9911     i = backwardMostMove;
9912     linelen = 0;
9913     newblock = TRUE;
9914
9915     while (i < forwardMostMove) {
9916         /* Print comments preceding this move */
9917         if (commentList[i] != NULL) {
9918             if (linelen > 0) fprintf(f, "\n");
9919             fprintf(f, "%s", commentList[i]);
9920             linelen = 0;
9921             newblock = TRUE;
9922         }
9923
9924         /* Format move number */
9925         if ((i % 2) == 0) {
9926             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9927         } else {
9928             if (newblock) {
9929                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9930             } else {
9931                 numtext[0] = NULLCHAR;
9932             }
9933         }
9934         numlen = strlen(numtext);
9935         newblock = FALSE;
9936
9937         /* Print move number */
9938         blank = linelen > 0 && numlen > 0;
9939         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9940             fprintf(f, "\n");
9941             linelen = 0;
9942             blank = 0;
9943         }
9944         if (blank) {
9945             fprintf(f, " ");
9946             linelen++;
9947         }
9948         fprintf(f, "%s", numtext);
9949         linelen += numlen;
9950
9951         /* Get move */
9952         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9953         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9954
9955         /* Print move */
9956         blank = linelen > 0 && movelen > 0;
9957         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9958             fprintf(f, "\n");
9959             linelen = 0;
9960             blank = 0;
9961         }
9962         if (blank) {
9963             fprintf(f, " ");
9964             linelen++;
9965         }
9966         fprintf(f, "%s", move_buffer);
9967         linelen += movelen;
9968
9969         /* [AS] Add PV info if present */
9970         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9971             /* [HGM] add time */
9972             char buf[MSG_SIZ]; int seconds;
9973
9974             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9975
9976             if( seconds <= 0) buf[0] = 0; else
9977             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9978                 seconds = (seconds + 4)/10; // round to full seconds
9979                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9980                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9981             }
9982
9983             sprintf( move_buffer, "{%s%.2f/%d%s}",
9984                 pvInfoList[i].score >= 0 ? "+" : "",
9985                 pvInfoList[i].score / 100.0,
9986                 pvInfoList[i].depth,
9987                 buf );
9988
9989             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9990
9991             /* Print score/depth */
9992             blank = linelen > 0 && movelen > 0;
9993             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9994                 fprintf(f, "\n");
9995                 linelen = 0;
9996                 blank = 0;
9997             }
9998             if (blank) {
9999                 fprintf(f, " ");
10000                 linelen++;
10001             }
10002             fprintf(f, "%s", move_buffer);
10003             linelen += movelen;
10004         }
10005
10006         i++;
10007     }
10008
10009     /* Start a new line */
10010     if (linelen > 0) fprintf(f, "\n");
10011
10012     /* Print comments after last move */
10013     if (commentList[i] != NULL) {
10014         fprintf(f, "%s\n", commentList[i]);
10015     }
10016
10017     /* Print result */
10018     if (gameInfo.resultDetails != NULL &&
10019         gameInfo.resultDetails[0] != NULLCHAR) {
10020         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10021                 PGNResult(gameInfo.result));
10022     } else {
10023         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10024     }
10025
10026     fclose(f);
10027     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10028     return TRUE;
10029 }
10030
10031 /* Save game in old style and close the file */
10032 int
10033 SaveGameOldStyle(f)
10034      FILE *f;
10035 {
10036     int i, offset;
10037     time_t tm;
10038
10039     tm = time((time_t *) NULL);
10040
10041     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10042     PrintOpponents(f);
10043
10044     if (backwardMostMove > 0 || startedFromSetupPosition) {
10045         fprintf(f, "\n[--------------\n");
10046         PrintPosition(f, backwardMostMove);
10047         fprintf(f, "--------------]\n");
10048     } else {
10049         fprintf(f, "\n");
10050     }
10051
10052     i = backwardMostMove;
10053     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10054
10055     while (i < forwardMostMove) {
10056         if (commentList[i] != NULL) {
10057             fprintf(f, "[%s]\n", commentList[i]);
10058         }
10059
10060         if ((i % 2) == 1) {
10061             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10062             i++;
10063         } else {
10064             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10065             i++;
10066             if (commentList[i] != NULL) {
10067                 fprintf(f, "\n");
10068                 continue;
10069             }
10070             if (i >= forwardMostMove) {
10071                 fprintf(f, "\n");
10072                 break;
10073             }
10074             fprintf(f, "%s\n", parseList[i]);
10075             i++;
10076         }
10077     }
10078
10079     if (commentList[i] != NULL) {
10080         fprintf(f, "[%s]\n", commentList[i]);
10081     }
10082
10083     /* This isn't really the old style, but it's close enough */
10084     if (gameInfo.resultDetails != NULL &&
10085         gameInfo.resultDetails[0] != NULLCHAR) {
10086         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10087                 gameInfo.resultDetails);
10088     } else {
10089         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10090     }
10091
10092     fclose(f);
10093     return TRUE;
10094 }
10095
10096 /* Save the current game to open file f and close the file */
10097 int
10098 SaveGame(f, dummy, dummy2)
10099      FILE *f;
10100      int dummy;
10101      char *dummy2;
10102 {
10103     if (gameMode == EditPosition) EditPositionDone(TRUE);
10104     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10105     if (appData.oldSaveStyle)
10106       return SaveGameOldStyle(f);
10107     else
10108       return SaveGamePGN(f);
10109 }
10110
10111 /* Save the current position to the given file */
10112 int
10113 SavePositionToFile(filename)
10114      char *filename;
10115 {
10116     FILE *f;
10117     char buf[MSG_SIZ];
10118
10119     if (strcmp(filename, "-") == 0) {
10120         return SavePosition(stdout, 0, NULL);
10121     } else {
10122         f = fopen(filename, "a");
10123         if (f == NULL) {
10124             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10125             DisplayError(buf, errno);
10126             return FALSE;
10127         } else {
10128             SavePosition(f, 0, NULL);
10129             return TRUE;
10130         }
10131     }
10132 }
10133
10134 /* Save the current position to the given open file and close the file */
10135 int
10136 SavePosition(f, dummy, dummy2)
10137      FILE *f;
10138      int dummy;
10139      char *dummy2;
10140 {
10141     time_t tm;
10142     char *fen;
10143     if (gameMode == EditPosition) EditPositionDone(TRUE);
10144     if (appData.oldSaveStyle) {
10145         tm = time((time_t *) NULL);
10146
10147         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10148         PrintOpponents(f);
10149         fprintf(f, "[--------------\n");
10150         PrintPosition(f, currentMove);
10151         fprintf(f, "--------------]\n");
10152     } else {
10153         fen = PositionToFEN(currentMove, NULL);
10154         fprintf(f, "%s\n", fen);
10155         free(fen);
10156     }
10157     fclose(f);
10158     return TRUE;
10159 }
10160
10161 void
10162 ReloadCmailMsgEvent(unregister)
10163      int unregister;
10164 {
10165 #if !WIN32
10166     static char *inFilename = NULL;
10167     static char *outFilename;
10168     int i;
10169     struct stat inbuf, outbuf;
10170     int status;
10171
10172     /* Any registered moves are unregistered if unregister is set, */
10173     /* i.e. invoked by the signal handler */
10174     if (unregister) {
10175         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10176             cmailMoveRegistered[i] = FALSE;
10177             if (cmailCommentList[i] != NULL) {
10178                 free(cmailCommentList[i]);
10179                 cmailCommentList[i] = NULL;
10180             }
10181         }
10182         nCmailMovesRegistered = 0;
10183     }
10184
10185     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10186         cmailResult[i] = CMAIL_NOT_RESULT;
10187     }
10188     nCmailResults = 0;
10189
10190     if (inFilename == NULL) {
10191         /* Because the filenames are static they only get malloced once  */
10192         /* and they never get freed                                      */
10193         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10194         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10195
10196         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10197         sprintf(outFilename, "%s.out", appData.cmailGameName);
10198     }
10199
10200     status = stat(outFilename, &outbuf);
10201     if (status < 0) {
10202         cmailMailedMove = FALSE;
10203     } else {
10204         status = stat(inFilename, &inbuf);
10205         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10206     }
10207
10208     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10209        counts the games, notes how each one terminated, etc.
10210
10211        It would be nice to remove this kludge and instead gather all
10212        the information while building the game list.  (And to keep it
10213        in the game list nodes instead of having a bunch of fixed-size
10214        parallel arrays.)  Note this will require getting each game's
10215        termination from the PGN tags, as the game list builder does
10216        not process the game moves.  --mann
10217        */
10218     cmailMsgLoaded = TRUE;
10219     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10220
10221     /* Load first game in the file or popup game menu */
10222     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10223
10224 #endif /* !WIN32 */
10225     return;
10226 }
10227
10228 int
10229 RegisterMove()
10230 {
10231     FILE *f;
10232     char string[MSG_SIZ];
10233
10234     if (   cmailMailedMove
10235         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10236         return TRUE;            /* Allow free viewing  */
10237     }
10238
10239     /* Unregister move to ensure that we don't leave RegisterMove        */
10240     /* with the move registered when the conditions for registering no   */
10241     /* longer hold                                                       */
10242     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10243         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10244         nCmailMovesRegistered --;
10245
10246         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10247           {
10248               free(cmailCommentList[lastLoadGameNumber - 1]);
10249               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10250           }
10251     }
10252
10253     if (cmailOldMove == -1) {
10254         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10255         return FALSE;
10256     }
10257
10258     if (currentMove > cmailOldMove + 1) {
10259         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10260         return FALSE;
10261     }
10262
10263     if (currentMove < cmailOldMove) {
10264         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10265         return FALSE;
10266     }
10267
10268     if (forwardMostMove > currentMove) {
10269         /* Silently truncate extra moves */
10270         TruncateGame();
10271     }
10272
10273     if (   (currentMove == cmailOldMove + 1)
10274         || (   (currentMove == cmailOldMove)
10275             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10276                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10277         if (gameInfo.result != GameUnfinished) {
10278             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10279         }
10280
10281         if (commentList[currentMove] != NULL) {
10282             cmailCommentList[lastLoadGameNumber - 1]
10283               = StrSave(commentList[currentMove]);
10284         }
10285         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10286
10287         if (appData.debugMode)
10288           fprintf(debugFP, "Saving %s for game %d\n",
10289                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10290
10291         sprintf(string,
10292                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10293
10294         f = fopen(string, "w");
10295         if (appData.oldSaveStyle) {
10296             SaveGameOldStyle(f); /* also closes the file */
10297
10298             sprintf(string, "%s.pos.out", appData.cmailGameName);
10299             f = fopen(string, "w");
10300             SavePosition(f, 0, NULL); /* also closes the file */
10301         } else {
10302             fprintf(f, "{--------------\n");
10303             PrintPosition(f, currentMove);
10304             fprintf(f, "--------------}\n\n");
10305
10306             SaveGame(f, 0, NULL); /* also closes the file*/
10307         }
10308
10309         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10310         nCmailMovesRegistered ++;
10311     } else if (nCmailGames == 1) {
10312         DisplayError(_("You have not made a move yet"), 0);
10313         return FALSE;
10314     }
10315
10316     return TRUE;
10317 }
10318
10319 void
10320 MailMoveEvent()
10321 {
10322 #if !WIN32
10323     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10324     FILE *commandOutput;
10325     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10326     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10327     int nBuffers;
10328     int i;
10329     int archived;
10330     char *arcDir;
10331
10332     if (! cmailMsgLoaded) {
10333         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10334         return;
10335     }
10336
10337     if (nCmailGames == nCmailResults) {
10338         DisplayError(_("No unfinished games"), 0);
10339         return;
10340     }
10341
10342 #if CMAIL_PROHIBIT_REMAIL
10343     if (cmailMailedMove) {
10344         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);
10345         DisplayError(msg, 0);
10346         return;
10347     }
10348 #endif
10349
10350     if (! (cmailMailedMove || RegisterMove())) return;
10351
10352     if (   cmailMailedMove
10353         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10354         sprintf(string, partCommandString,
10355                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10356         commandOutput = popen(string, "r");
10357
10358         if (commandOutput == NULL) {
10359             DisplayError(_("Failed to invoke cmail"), 0);
10360         } else {
10361             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10362                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10363             }
10364             if (nBuffers > 1) {
10365                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10366                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10367                 nBytes = MSG_SIZ - 1;
10368             } else {
10369                 (void) memcpy(msg, buffer, nBytes);
10370             }
10371             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10372
10373             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10374                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10375
10376                 archived = TRUE;
10377                 for (i = 0; i < nCmailGames; i ++) {
10378                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10379                         archived = FALSE;
10380                     }
10381                 }
10382                 if (   archived
10383                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10384                         != NULL)) {
10385                     sprintf(buffer, "%s/%s.%s.archive",
10386                             arcDir,
10387                             appData.cmailGameName,
10388                             gameInfo.date);
10389                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10390                     cmailMsgLoaded = FALSE;
10391                 }
10392             }
10393
10394             DisplayInformation(msg);
10395             pclose(commandOutput);
10396         }
10397     } else {
10398         if ((*cmailMsg) != '\0') {
10399             DisplayInformation(cmailMsg);
10400         }
10401     }
10402
10403     return;
10404 #endif /* !WIN32 */
10405 }
10406
10407 char *
10408 CmailMsg()
10409 {
10410 #if WIN32
10411     return NULL;
10412 #else
10413     int  prependComma = 0;
10414     char number[5];
10415     char string[MSG_SIZ];       /* Space for game-list */
10416     int  i;
10417
10418     if (!cmailMsgLoaded) return "";
10419
10420     if (cmailMailedMove) {
10421         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10422     } else {
10423         /* Create a list of games left */
10424         sprintf(string, "[");
10425         for (i = 0; i < nCmailGames; i ++) {
10426             if (! (   cmailMoveRegistered[i]
10427                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10428                 if (prependComma) {
10429                     sprintf(number, ",%d", i + 1);
10430                 } else {
10431                     sprintf(number, "%d", i + 1);
10432                     prependComma = 1;
10433                 }
10434
10435                 strcat(string, number);
10436             }
10437         }
10438         strcat(string, "]");
10439
10440         if (nCmailMovesRegistered + nCmailResults == 0) {
10441             switch (nCmailGames) {
10442               case 1:
10443                 sprintf(cmailMsg,
10444                         _("Still need to make move for game\n"));
10445                 break;
10446
10447               case 2:
10448                 sprintf(cmailMsg,
10449                         _("Still need to make moves for both games\n"));
10450                 break;
10451
10452               default:
10453                 sprintf(cmailMsg,
10454                         _("Still need to make moves for all %d games\n"),
10455                         nCmailGames);
10456                 break;
10457             }
10458         } else {
10459             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10460               case 1:
10461                 sprintf(cmailMsg,
10462                         _("Still need to make a move for game %s\n"),
10463                         string);
10464                 break;
10465
10466               case 0:
10467                 if (nCmailResults == nCmailGames) {
10468                     sprintf(cmailMsg, _("No unfinished games\n"));
10469                 } else {
10470                     sprintf(cmailMsg, _("Ready to send mail\n"));
10471                 }
10472                 break;
10473
10474               default:
10475                 sprintf(cmailMsg,
10476                         _("Still need to make moves for games %s\n"),
10477                         string);
10478             }
10479         }
10480     }
10481     return cmailMsg;
10482 #endif /* WIN32 */
10483 }
10484
10485 void
10486 ResetGameEvent()
10487 {
10488     if (gameMode == Training)
10489       SetTrainingModeOff();
10490
10491     Reset(TRUE, TRUE);
10492     cmailMsgLoaded = FALSE;
10493     if (appData.icsActive) {
10494       SendToICS(ics_prefix);
10495       SendToICS("refresh\n");
10496     }
10497 }
10498
10499 void
10500 ExitEvent(status)
10501      int status;
10502 {
10503     exiting++;
10504     if (exiting > 2) {
10505       /* Give up on clean exit */
10506       exit(status);
10507     }
10508     if (exiting > 1) {
10509       /* Keep trying for clean exit */
10510       return;
10511     }
10512
10513     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10514
10515     if (telnetISR != NULL) {
10516       RemoveInputSource(telnetISR);
10517     }
10518     if (icsPR != NoProc) {
10519       DestroyChildProcess(icsPR, TRUE);
10520     }
10521
10522     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10523     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10524
10525     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10526     /* make sure this other one finishes before killing it!                  */
10527     if(endingGame) { int count = 0;
10528         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10529         while(endingGame && count++ < 10) DoSleep(1);
10530         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10531     }
10532
10533     /* Kill off chess programs */
10534     if (first.pr != NoProc) {
10535         ExitAnalyzeMode();
10536
10537         DoSleep( appData.delayBeforeQuit );
10538         SendToProgram("quit\n", &first);
10539         DoSleep( appData.delayAfterQuit );
10540         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10541     }
10542     if (second.pr != NoProc) {
10543         DoSleep( appData.delayBeforeQuit );
10544         SendToProgram("quit\n", &second);
10545         DoSleep( appData.delayAfterQuit );
10546         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10547     }
10548     if (first.isr != NULL) {
10549         RemoveInputSource(first.isr);
10550     }
10551     if (second.isr != NULL) {
10552         RemoveInputSource(second.isr);
10553     }
10554
10555     ShutDownFrontEnd();
10556     exit(status);
10557 }
10558
10559 void
10560 PauseEvent()
10561 {
10562     if (appData.debugMode)
10563         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10564     if (pausing) {
10565         pausing = FALSE;
10566         ModeHighlight();
10567         if (gameMode == MachinePlaysWhite ||
10568             gameMode == MachinePlaysBlack) {
10569             StartClocks();
10570         } else {
10571             DisplayBothClocks();
10572         }
10573         if (gameMode == PlayFromGameFile) {
10574             if (appData.timeDelay >= 0)
10575                 AutoPlayGameLoop();
10576         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10577             Reset(FALSE, TRUE);
10578             SendToICS(ics_prefix);
10579             SendToICS("refresh\n");
10580         } else if (currentMove < forwardMostMove) {
10581             ForwardInner(forwardMostMove);
10582         }
10583         pauseExamInvalid = FALSE;
10584     } else {
10585         switch (gameMode) {
10586           default:
10587             return;
10588           case IcsExamining:
10589             pauseExamForwardMostMove = forwardMostMove;
10590             pauseExamInvalid = FALSE;
10591             /* fall through */
10592           case IcsObserving:
10593           case IcsPlayingWhite:
10594           case IcsPlayingBlack:
10595             pausing = TRUE;
10596             ModeHighlight();
10597             return;
10598           case PlayFromGameFile:
10599             (void) StopLoadGameTimer();
10600             pausing = TRUE;
10601             ModeHighlight();
10602             break;
10603           case BeginningOfGame:
10604             if (appData.icsActive) return;
10605             /* else fall through */
10606           case MachinePlaysWhite:
10607           case MachinePlaysBlack:
10608           case TwoMachinesPlay:
10609             if (forwardMostMove == 0)
10610               return;           /* don't pause if no one has moved */
10611             if ((gameMode == MachinePlaysWhite &&
10612                  !WhiteOnMove(forwardMostMove)) ||
10613                 (gameMode == MachinePlaysBlack &&
10614                  WhiteOnMove(forwardMostMove))) {
10615                 StopClocks();
10616             }
10617             pausing = TRUE;
10618             ModeHighlight();
10619             break;
10620         }
10621     }
10622 }
10623
10624 void
10625 EditCommentEvent()
10626 {
10627     char title[MSG_SIZ];
10628
10629     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10630         strcpy(title, _("Edit comment"));
10631     } else {
10632         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10633                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10634                 parseList[currentMove - 1]);
10635     }
10636
10637     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10638 }
10639
10640
10641 void
10642 EditTagsEvent()
10643 {
10644     char *tags = PGNTags(&gameInfo);
10645     EditTagsPopUp(tags);
10646     free(tags);
10647 }
10648
10649 void
10650 AnalyzeModeEvent()
10651 {
10652     if (appData.noChessProgram || gameMode == AnalyzeMode)
10653       return;
10654
10655     if (gameMode != AnalyzeFile) {
10656         if (!appData.icsEngineAnalyze) {
10657                EditGameEvent();
10658                if (gameMode != EditGame) return;
10659         }
10660         ResurrectChessProgram();
10661         SendToProgram("analyze\n", &first);
10662         first.analyzing = TRUE;
10663         /*first.maybeThinking = TRUE;*/
10664         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10665         EngineOutputPopUp();
10666     }
10667     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10668     pausing = FALSE;
10669     ModeHighlight();
10670     SetGameInfo();
10671
10672     StartAnalysisClock();
10673     GetTimeMark(&lastNodeCountTime);
10674     lastNodeCount = 0;
10675 }
10676
10677 void
10678 AnalyzeFileEvent()
10679 {
10680     if (appData.noChessProgram || gameMode == AnalyzeFile)
10681       return;
10682
10683     if (gameMode != AnalyzeMode) {
10684         EditGameEvent();
10685         if (gameMode != EditGame) return;
10686         ResurrectChessProgram();
10687         SendToProgram("analyze\n", &first);
10688         first.analyzing = TRUE;
10689         /*first.maybeThinking = TRUE;*/
10690         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10691         EngineOutputPopUp();
10692     }
10693     gameMode = AnalyzeFile;
10694     pausing = FALSE;
10695     ModeHighlight();
10696     SetGameInfo();
10697
10698     StartAnalysisClock();
10699     GetTimeMark(&lastNodeCountTime);
10700     lastNodeCount = 0;
10701 }
10702
10703 void
10704 MachineWhiteEvent()
10705 {
10706     char buf[MSG_SIZ];
10707     char *bookHit = NULL;
10708
10709     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10710       return;
10711
10712
10713     if (gameMode == PlayFromGameFile ||
10714         gameMode == TwoMachinesPlay  ||
10715         gameMode == Training         ||
10716         gameMode == AnalyzeMode      ||
10717         gameMode == EndOfGame)
10718         EditGameEvent();
10719
10720     if (gameMode == EditPosition) 
10721         EditPositionDone(TRUE);
10722
10723     if (!WhiteOnMove(currentMove)) {
10724         DisplayError(_("It is not White's turn"), 0);
10725         return;
10726     }
10727
10728     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10729       ExitAnalyzeMode();
10730
10731     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10732         gameMode == AnalyzeFile)
10733         TruncateGame();
10734
10735     ResurrectChessProgram();    /* in case it isn't running */
10736     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10737         gameMode = MachinePlaysWhite;
10738         ResetClocks();
10739     } else
10740     gameMode = MachinePlaysWhite;
10741     pausing = FALSE;
10742     ModeHighlight();
10743     SetGameInfo();
10744     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10745     DisplayTitle(buf);
10746     if (first.sendName) {
10747       sprintf(buf, "name %s\n", gameInfo.black);
10748       SendToProgram(buf, &first);
10749     }
10750     if (first.sendTime) {
10751       if (first.useColors) {
10752         SendToProgram("black\n", &first); /*gnu kludge*/
10753       }
10754       SendTimeRemaining(&first, TRUE);
10755     }
10756     if (first.useColors) {
10757       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10758     }
10759     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10760     SetMachineThinkingEnables();
10761     first.maybeThinking = TRUE;
10762     StartClocks();
10763     firstMove = FALSE;
10764
10765     if (appData.autoFlipView && !flipView) {
10766       flipView = !flipView;
10767       DrawPosition(FALSE, NULL);
10768       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10769     }
10770
10771     if(bookHit) { // [HGM] book: simulate book reply
10772         static char bookMove[MSG_SIZ]; // a bit generous?
10773
10774         programStats.nodes = programStats.depth = programStats.time =
10775         programStats.score = programStats.got_only_move = 0;
10776         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10777
10778         strcpy(bookMove, "move ");
10779         strcat(bookMove, bookHit);
10780         HandleMachineMove(bookMove, &first);
10781     }
10782 }
10783
10784 void
10785 MachineBlackEvent()
10786 {
10787     char buf[MSG_SIZ];
10788    char *bookHit = NULL;
10789
10790     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10791         return;
10792
10793
10794     if (gameMode == PlayFromGameFile ||
10795         gameMode == TwoMachinesPlay  ||
10796         gameMode == Training         ||
10797         gameMode == AnalyzeMode      ||
10798         gameMode == EndOfGame)
10799         EditGameEvent();
10800
10801     if (gameMode == EditPosition) 
10802         EditPositionDone(TRUE);
10803
10804     if (WhiteOnMove(currentMove)) {
10805         DisplayError(_("It is not Black's turn"), 0);
10806         return;
10807     }
10808
10809     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10810       ExitAnalyzeMode();
10811
10812     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10813         gameMode == AnalyzeFile)
10814         TruncateGame();
10815
10816     ResurrectChessProgram();    /* in case it isn't running */
10817     gameMode = MachinePlaysBlack;
10818     pausing = FALSE;
10819     ModeHighlight();
10820     SetGameInfo();
10821     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10822     DisplayTitle(buf);
10823     if (first.sendName) {
10824       sprintf(buf, "name %s\n", gameInfo.white);
10825       SendToProgram(buf, &first);
10826     }
10827     if (first.sendTime) {
10828       if (first.useColors) {
10829         SendToProgram("white\n", &first); /*gnu kludge*/
10830       }
10831       SendTimeRemaining(&first, FALSE);
10832     }
10833     if (first.useColors) {
10834       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10835     }
10836     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10837     SetMachineThinkingEnables();
10838     first.maybeThinking = TRUE;
10839     StartClocks();
10840
10841     if (appData.autoFlipView && flipView) {
10842       flipView = !flipView;
10843       DrawPosition(FALSE, NULL);
10844       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10845     }
10846     if(bookHit) { // [HGM] book: simulate book reply
10847         static char bookMove[MSG_SIZ]; // a bit generous?
10848
10849         programStats.nodes = programStats.depth = programStats.time =
10850         programStats.score = programStats.got_only_move = 0;
10851         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10852
10853         strcpy(bookMove, "move ");
10854         strcat(bookMove, bookHit);
10855         HandleMachineMove(bookMove, &first);
10856     }
10857 }
10858
10859
10860 void
10861 DisplayTwoMachinesTitle()
10862 {
10863     char buf[MSG_SIZ];
10864     if (appData.matchGames > 0) {
10865         if (first.twoMachinesColor[0] == 'w') {
10866             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10867                     gameInfo.white, gameInfo.black,
10868                     first.matchWins, second.matchWins,
10869                     matchGame - 1 - (first.matchWins + second.matchWins));
10870         } else {
10871             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10872                     gameInfo.white, gameInfo.black,
10873                     second.matchWins, first.matchWins,
10874                     matchGame - 1 - (first.matchWins + second.matchWins));
10875         }
10876     } else {
10877         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10878     }
10879     DisplayTitle(buf);
10880 }
10881
10882 void
10883 TwoMachinesEvent P((void))
10884 {
10885     int i;
10886     char buf[MSG_SIZ];
10887     ChessProgramState *onmove;
10888     char *bookHit = NULL;
10889
10890     if (appData.noChessProgram) return;
10891
10892     switch (gameMode) {
10893       case TwoMachinesPlay:
10894         return;
10895       case MachinePlaysWhite:
10896       case MachinePlaysBlack:
10897         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10898             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10899             return;
10900         }
10901         /* fall through */
10902       case BeginningOfGame:
10903       case PlayFromGameFile:
10904       case EndOfGame:
10905         EditGameEvent();
10906         if (gameMode != EditGame) return;
10907         break;
10908       case EditPosition:
10909         EditPositionDone(TRUE);
10910         break;
10911       case AnalyzeMode:
10912       case AnalyzeFile:
10913         ExitAnalyzeMode();
10914         break;
10915       case EditGame:
10916       default:
10917         break;
10918     }
10919
10920 //    forwardMostMove = currentMove;
10921     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10922     ResurrectChessProgram();    /* in case first program isn't running */
10923
10924     if (second.pr == NULL) {
10925         StartChessProgram(&second);
10926         if (second.protocolVersion == 1) {
10927           TwoMachinesEventIfReady();
10928         } else {
10929           /* kludge: allow timeout for initial "feature" command */
10930           FreezeUI();
10931           DisplayMessage("", _("Starting second chess program"));
10932           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10933         }
10934         return;
10935     }
10936     DisplayMessage("", "");
10937     InitChessProgram(&second, FALSE);
10938     SendToProgram("force\n", &second);
10939     if (startedFromSetupPosition) {
10940         SendBoard(&second, backwardMostMove);
10941     if (appData.debugMode) {
10942         fprintf(debugFP, "Two Machines\n");
10943     }
10944     }
10945     for (i = backwardMostMove; i < forwardMostMove; i++) {
10946         SendMoveToProgram(i, &second);
10947     }
10948
10949     gameMode = TwoMachinesPlay;
10950     pausing = FALSE;
10951     ModeHighlight();
10952     SetGameInfo();
10953     DisplayTwoMachinesTitle();
10954     firstMove = TRUE;
10955     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10956         onmove = &first;
10957     } else {
10958         onmove = &second;
10959     }
10960
10961     SendToProgram(first.computerString, &first);
10962     if (first.sendName) {
10963       sprintf(buf, "name %s\n", second.tidy);
10964       SendToProgram(buf, &first);
10965     }
10966     SendToProgram(second.computerString, &second);
10967     if (second.sendName) {
10968       sprintf(buf, "name %s\n", first.tidy);
10969       SendToProgram(buf, &second);
10970     }
10971
10972     ResetClocks();
10973     if (!first.sendTime || !second.sendTime) {
10974         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10975         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10976     }
10977     if (onmove->sendTime) {
10978       if (onmove->useColors) {
10979         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10980       }
10981       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10982     }
10983     if (onmove->useColors) {
10984       SendToProgram(onmove->twoMachinesColor, onmove);
10985     }
10986     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10987 //    SendToProgram("go\n", onmove);
10988     onmove->maybeThinking = TRUE;
10989     SetMachineThinkingEnables();
10990
10991     StartClocks();
10992
10993     if(bookHit) { // [HGM] book: simulate book reply
10994         static char bookMove[MSG_SIZ]; // a bit generous?
10995
10996         programStats.nodes = programStats.depth = programStats.time =
10997         programStats.score = programStats.got_only_move = 0;
10998         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10999
11000         strcpy(bookMove, "move ");
11001         strcat(bookMove, bookHit);
11002         savedMessage = bookMove; // args for deferred call
11003         savedState = onmove;
11004         ScheduleDelayedEvent(DeferredBookMove, 1);
11005     }
11006 }
11007
11008 void
11009 TrainingEvent()
11010 {
11011     if (gameMode == Training) {
11012       SetTrainingModeOff();
11013       gameMode = PlayFromGameFile;
11014       DisplayMessage("", _("Training mode off"));
11015     } else {
11016       gameMode = Training;
11017       animateTraining = appData.animate;
11018
11019       /* make sure we are not already at the end of the game */
11020       if (currentMove < forwardMostMove) {
11021         SetTrainingModeOn();
11022         DisplayMessage("", _("Training mode on"));
11023       } else {
11024         gameMode = PlayFromGameFile;
11025         DisplayError(_("Already at end of game"), 0);
11026       }
11027     }
11028     ModeHighlight();
11029 }
11030
11031 void
11032 IcsClientEvent()
11033 {
11034     if (!appData.icsActive) return;
11035     switch (gameMode) {
11036       case IcsPlayingWhite:
11037       case IcsPlayingBlack:
11038       case IcsObserving:
11039       case IcsIdle:
11040       case BeginningOfGame:
11041       case IcsExamining:
11042         return;
11043
11044       case EditGame:
11045         break;
11046
11047       case EditPosition:
11048         EditPositionDone(TRUE);
11049         break;
11050
11051       case AnalyzeMode:
11052       case AnalyzeFile:
11053         ExitAnalyzeMode();
11054         break;
11055
11056       default:
11057         EditGameEvent();
11058         break;
11059     }
11060
11061     gameMode = IcsIdle;
11062     ModeHighlight();
11063     return;
11064 }
11065
11066
11067 void
11068 EditGameEvent()
11069 {
11070     int i;
11071
11072     switch (gameMode) {
11073       case Training:
11074         SetTrainingModeOff();
11075         break;
11076       case MachinePlaysWhite:
11077       case MachinePlaysBlack:
11078       case BeginningOfGame:
11079         SendToProgram("force\n", &first);
11080         SetUserThinkingEnables();
11081         break;
11082       case PlayFromGameFile:
11083         (void) StopLoadGameTimer();
11084         if (gameFileFP != NULL) {
11085             gameFileFP = NULL;
11086         }
11087         break;
11088       case EditPosition:
11089         EditPositionDone(TRUE);
11090         break;
11091       case AnalyzeMode:
11092       case AnalyzeFile:
11093         ExitAnalyzeMode();
11094         SendToProgram("force\n", &first);
11095         break;
11096       case TwoMachinesPlay:
11097         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11098         ResurrectChessProgram();
11099         SetUserThinkingEnables();
11100         break;
11101       case EndOfGame:
11102         ResurrectChessProgram();
11103         break;
11104       case IcsPlayingBlack:
11105       case IcsPlayingWhite:
11106         DisplayError(_("Warning: You are still playing a game"), 0);
11107         break;
11108       case IcsObserving:
11109         DisplayError(_("Warning: You are still observing a game"), 0);
11110         break;
11111       case IcsExamining:
11112         DisplayError(_("Warning: You are still examining a game"), 0);
11113         break;
11114       case IcsIdle:
11115         break;
11116       case EditGame:
11117       default:
11118         return;
11119     }
11120
11121     pausing = FALSE;
11122     StopClocks();
11123     first.offeredDraw = second.offeredDraw = 0;
11124
11125     if (gameMode == PlayFromGameFile) {
11126         whiteTimeRemaining = timeRemaining[0][currentMove];
11127         blackTimeRemaining = timeRemaining[1][currentMove];
11128         DisplayTitle("");
11129     }
11130
11131     if (gameMode == MachinePlaysWhite ||
11132         gameMode == MachinePlaysBlack ||
11133         gameMode == TwoMachinesPlay ||
11134         gameMode == EndOfGame) {
11135         i = forwardMostMove;
11136         while (i > currentMove) {
11137             SendToProgram("undo\n", &first);
11138             i--;
11139         }
11140         whiteTimeRemaining = timeRemaining[0][currentMove];
11141         blackTimeRemaining = timeRemaining[1][currentMove];
11142         DisplayBothClocks();
11143         if (whiteFlag || blackFlag) {
11144             whiteFlag = blackFlag = 0;
11145         }
11146         DisplayTitle("");
11147     }
11148
11149     gameMode = EditGame;
11150     ModeHighlight();
11151     SetGameInfo();
11152 }
11153
11154
11155 void
11156 EditPositionEvent()
11157 {
11158     if (gameMode == EditPosition) {
11159         EditGameEvent();
11160         return;
11161     }
11162
11163     EditGameEvent();
11164     if (gameMode != EditGame) return;
11165
11166     gameMode = EditPosition;
11167     ModeHighlight();
11168     SetGameInfo();
11169     if (currentMove > 0)
11170       CopyBoard(boards[0], boards[currentMove]);
11171
11172     blackPlaysFirst = !WhiteOnMove(currentMove);
11173     ResetClocks();
11174     currentMove = forwardMostMove = backwardMostMove = 0;
11175     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11176     DisplayMove(-1);
11177 }
11178
11179 void
11180 ExitAnalyzeMode()
11181 {
11182     /* [DM] icsEngineAnalyze - possible call from other functions */
11183     if (appData.icsEngineAnalyze) {
11184         appData.icsEngineAnalyze = FALSE;
11185
11186         DisplayMessage("",_("Close ICS engine analyze..."));
11187     }
11188     if (first.analysisSupport && first.analyzing) {
11189       SendToProgram("exit\n", &first);
11190       first.analyzing = FALSE;
11191     }
11192     thinkOutput[0] = NULLCHAR;
11193 }
11194
11195 void
11196 EditPositionDone(Boolean fakeRights)
11197 {
11198     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11199
11200     startedFromSetupPosition = TRUE;
11201     InitChessProgram(&first, FALSE);
11202     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11203       boards[0][EP_STATUS] = EP_NONE;
11204       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11205     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11206         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11207         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11208       } else boards[0][CASTLING][2] = NoRights;
11209     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11210         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11211         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11212       } else boards[0][CASTLING][5] = NoRights;
11213     }
11214     SendToProgram("force\n", &first);
11215     if (blackPlaysFirst) {
11216         strcpy(moveList[0], "");
11217         strcpy(parseList[0], "");
11218         currentMove = forwardMostMove = backwardMostMove = 1;
11219         CopyBoard(boards[1], boards[0]);
11220     } else {
11221         currentMove = forwardMostMove = backwardMostMove = 0;
11222     }
11223     SendBoard(&first, forwardMostMove);
11224     if (appData.debugMode) {
11225         fprintf(debugFP, "EditPosDone\n");
11226     }
11227     DisplayTitle("");
11228     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11229     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11230     gameMode = EditGame;
11231     ModeHighlight();
11232     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11233     ClearHighlights(); /* [AS] */
11234 }
11235
11236 /* Pause for `ms' milliseconds */
11237 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11238 void
11239 TimeDelay(ms)
11240      long ms;
11241 {
11242     TimeMark m1, m2;
11243
11244     GetTimeMark(&m1);
11245     do {
11246         GetTimeMark(&m2);
11247     } while (SubtractTimeMarks(&m2, &m1) < ms);
11248 }
11249
11250 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11251 void
11252 SendMultiLineToICS(buf)
11253      char *buf;
11254 {
11255     char temp[MSG_SIZ+1], *p;
11256     int len;
11257
11258     len = strlen(buf);
11259     if (len > MSG_SIZ)
11260       len = MSG_SIZ;
11261
11262     strncpy(temp, buf, len);
11263     temp[len] = 0;
11264
11265     p = temp;
11266     while (*p) {
11267         if (*p == '\n' || *p == '\r')
11268           *p = ' ';
11269         ++p;
11270     }
11271
11272     strcat(temp, "\n");
11273     SendToICS(temp);
11274     SendToPlayer(temp, strlen(temp));
11275 }
11276
11277 void
11278 SetWhiteToPlayEvent()
11279 {
11280     if (gameMode == EditPosition) {
11281         blackPlaysFirst = FALSE;
11282         DisplayBothClocks();    /* works because currentMove is 0 */
11283     } else if (gameMode == IcsExamining) {
11284         SendToICS(ics_prefix);
11285         SendToICS("tomove white\n");
11286     }
11287 }
11288
11289 void
11290 SetBlackToPlayEvent()
11291 {
11292     if (gameMode == EditPosition) {
11293         blackPlaysFirst = TRUE;
11294         currentMove = 1;        /* kludge */
11295         DisplayBothClocks();
11296         currentMove = 0;
11297     } else if (gameMode == IcsExamining) {
11298         SendToICS(ics_prefix);
11299         SendToICS("tomove black\n");
11300     }
11301 }
11302
11303 void
11304 EditPositionMenuEvent(selection, x, y)
11305      ChessSquare selection;
11306      int x, y;
11307 {
11308     char buf[MSG_SIZ];
11309     ChessSquare piece = boards[0][y][x];
11310
11311     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11312
11313     switch (selection) {
11314       case ClearBoard:
11315         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11316             SendToICS(ics_prefix);
11317             SendToICS("bsetup clear\n");
11318         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11319             SendToICS(ics_prefix);
11320             SendToICS("clearboard\n");
11321         } else {
11322             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11323                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11324                 for (y = 0; y < BOARD_HEIGHT; y++) {
11325                     if (gameMode == IcsExamining) {
11326                         if (boards[currentMove][y][x] != EmptySquare) {
11327                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11328                                     AAA + x, ONE + y);
11329                             SendToICS(buf);
11330                         }
11331                     } else {
11332                         boards[0][y][x] = p;
11333                     }
11334                 }
11335             }
11336         }
11337         if (gameMode == EditPosition) {
11338             DrawPosition(FALSE, boards[0]);
11339         }
11340         break;
11341
11342       case WhitePlay:
11343         SetWhiteToPlayEvent();
11344         break;
11345
11346       case BlackPlay:
11347         SetBlackToPlayEvent();
11348         break;
11349
11350       case EmptySquare:
11351         if (gameMode == IcsExamining) {
11352             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11353             SendToICS(buf);
11354         } else {
11355             boards[0][y][x] = EmptySquare;
11356             DrawPosition(FALSE, boards[0]);
11357         }
11358         break;
11359
11360       case PromotePiece:
11361         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11362            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11363             selection = (ChessSquare) (PROMOTED piece);
11364         } else if(piece == EmptySquare) selection = WhiteSilver;
11365         else selection = (ChessSquare)((int)piece - 1);
11366         goto defaultlabel;
11367
11368       case DemotePiece:
11369         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11370            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11371             selection = (ChessSquare) (DEMOTED piece);
11372         } else if(piece == EmptySquare) selection = BlackSilver;
11373         else selection = (ChessSquare)((int)piece + 1);
11374         goto defaultlabel;
11375
11376       case WhiteQueen:
11377       case BlackQueen:
11378         if(gameInfo.variant == VariantShatranj ||
11379            gameInfo.variant == VariantXiangqi  ||
11380            gameInfo.variant == VariantCourier    )
11381             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11382         goto defaultlabel;
11383
11384       case WhiteKing:
11385       case BlackKing:
11386         if(gameInfo.variant == VariantXiangqi)
11387             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11388         if(gameInfo.variant == VariantKnightmate)
11389             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11390       default:
11391         defaultlabel:
11392         if (gameMode == IcsExamining) {
11393             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11394                     PieceToChar(selection), AAA + x, ONE + y);
11395             SendToICS(buf);
11396         } else {
11397             boards[0][y][x] = selection;
11398             DrawPosition(FALSE, boards[0]);
11399         }
11400         break;
11401     }
11402 }
11403
11404
11405 void
11406 DropMenuEvent(selection, x, y)
11407      ChessSquare selection;
11408      int x, y;
11409 {
11410     ChessMove moveType;
11411
11412     switch (gameMode) {
11413       case IcsPlayingWhite:
11414       case MachinePlaysBlack:
11415         if (!WhiteOnMove(currentMove)) {
11416             DisplayMoveError(_("It is Black's turn"));
11417             return;
11418         }
11419         moveType = WhiteDrop;
11420         break;
11421       case IcsPlayingBlack:
11422       case MachinePlaysWhite:
11423         if (WhiteOnMove(currentMove)) {
11424             DisplayMoveError(_("It is White's turn"));
11425             return;
11426         }
11427         moveType = BlackDrop;
11428         break;
11429       case EditGame:
11430         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11431         break;
11432       default:
11433         return;
11434     }
11435
11436     if (moveType == BlackDrop && selection < BlackPawn) {
11437       selection = (ChessSquare) ((int) selection
11438                                  + (int) BlackPawn - (int) WhitePawn);
11439     }
11440     if (boards[currentMove][y][x] != EmptySquare) {
11441         DisplayMoveError(_("That square is occupied"));
11442         return;
11443     }
11444
11445     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11446 }
11447
11448 void
11449 AcceptEvent()
11450 {
11451     /* Accept a pending offer of any kind from opponent */
11452
11453     if (appData.icsActive) {
11454         SendToICS(ics_prefix);
11455         SendToICS("accept\n");
11456     } else if (cmailMsgLoaded) {
11457         if (currentMove == cmailOldMove &&
11458             commentList[cmailOldMove] != NULL &&
11459             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11460                    "Black offers a draw" : "White offers a draw")) {
11461             TruncateGame();
11462             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11463             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11464         } else {
11465             DisplayError(_("There is no pending offer on this move"), 0);
11466             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11467         }
11468     } else {
11469         /* Not used for offers from chess program */
11470     }
11471 }
11472
11473 void
11474 DeclineEvent()
11475 {
11476     /* Decline a pending offer of any kind from opponent */
11477
11478     if (appData.icsActive) {
11479         SendToICS(ics_prefix);
11480         SendToICS("decline\n");
11481     } else if (cmailMsgLoaded) {
11482         if (currentMove == cmailOldMove &&
11483             commentList[cmailOldMove] != NULL &&
11484             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11485                    "Black offers a draw" : "White offers a draw")) {
11486 #ifdef NOTDEF
11487             AppendComment(cmailOldMove, "Draw declined", TRUE);
11488             DisplayComment(cmailOldMove - 1, "Draw declined");
11489 #endif /*NOTDEF*/
11490         } else {
11491             DisplayError(_("There is no pending offer on this move"), 0);
11492         }
11493     } else {
11494         /* Not used for offers from chess program */
11495     }
11496 }
11497
11498 void
11499 RematchEvent()
11500 {
11501     /* Issue ICS rematch command */
11502     if (appData.icsActive) {
11503         SendToICS(ics_prefix);
11504         SendToICS("rematch\n");
11505     }
11506 }
11507
11508 void
11509 CallFlagEvent()
11510 {
11511     /* Call your opponent's flag (claim a win on time) */
11512     if (appData.icsActive) {
11513         SendToICS(ics_prefix);
11514         SendToICS("flag\n");
11515     } else {
11516         switch (gameMode) {
11517           default:
11518             return;
11519           case MachinePlaysWhite:
11520             if (whiteFlag) {
11521                 if (blackFlag)
11522                   GameEnds(GameIsDrawn, "Both players ran out of time",
11523                            GE_PLAYER);
11524                 else
11525                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11526             } else {
11527                 DisplayError(_("Your opponent is not out of time"), 0);
11528             }
11529             break;
11530           case MachinePlaysBlack:
11531             if (blackFlag) {
11532                 if (whiteFlag)
11533                   GameEnds(GameIsDrawn, "Both players ran out of time",
11534                            GE_PLAYER);
11535                 else
11536                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11537             } else {
11538                 DisplayError(_("Your opponent is not out of time"), 0);
11539             }
11540             break;
11541         }
11542     }
11543 }
11544
11545 void
11546 DrawEvent()
11547 {
11548     /* Offer draw or accept pending draw offer from opponent */
11549
11550     if (appData.icsActive) {
11551         /* Note: tournament rules require draw offers to be
11552            made after you make your move but before you punch
11553            your clock.  Currently ICS doesn't let you do that;
11554            instead, you immediately punch your clock after making
11555            a move, but you can offer a draw at any time. */
11556
11557         SendToICS(ics_prefix);
11558         SendToICS("draw\n");
11559     } else if (cmailMsgLoaded) {
11560         if (currentMove == cmailOldMove &&
11561             commentList[cmailOldMove] != NULL &&
11562             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11563                    "Black offers a draw" : "White offers a draw")) {
11564             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11565             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11566         } else if (currentMove == cmailOldMove + 1) {
11567             char *offer = WhiteOnMove(cmailOldMove) ?
11568               "White offers a draw" : "Black offers a draw";
11569             AppendComment(currentMove, offer, TRUE);
11570             DisplayComment(currentMove - 1, offer);
11571             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11572         } else {
11573             DisplayError(_("You must make your move before offering a draw"), 0);
11574             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11575         }
11576     } else if (first.offeredDraw) {
11577         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11578     } else {
11579         if (first.sendDrawOffers) {
11580             SendToProgram("draw\n", &first);
11581             userOfferedDraw = TRUE;
11582         }
11583     }
11584 }
11585
11586 void
11587 AdjournEvent()
11588 {
11589     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11590
11591     if (appData.icsActive) {
11592         SendToICS(ics_prefix);
11593         SendToICS("adjourn\n");
11594     } else {
11595         /* Currently GNU Chess doesn't offer or accept Adjourns */
11596     }
11597 }
11598
11599
11600 void
11601 AbortEvent()
11602 {
11603     /* Offer Abort or accept pending Abort offer from opponent */
11604
11605     if (appData.icsActive) {
11606         SendToICS(ics_prefix);
11607         SendToICS("abort\n");
11608     } else {
11609         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11610     }
11611 }
11612
11613 void
11614 ResignEvent()
11615 {
11616     /* Resign.  You can do this even if it's not your turn. */
11617
11618     if (appData.icsActive) {
11619         SendToICS(ics_prefix);
11620         SendToICS("resign\n");
11621     } else {
11622         switch (gameMode) {
11623           case MachinePlaysWhite:
11624             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11625             break;
11626           case MachinePlaysBlack:
11627             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11628             break;
11629           case EditGame:
11630             if (cmailMsgLoaded) {
11631                 TruncateGame();
11632                 if (WhiteOnMove(cmailOldMove)) {
11633                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11634                 } else {
11635                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11636                 }
11637                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11638             }
11639             break;
11640           default:
11641             break;
11642         }
11643     }
11644 }
11645
11646
11647 void
11648 StopObservingEvent()
11649 {
11650     /* Stop observing current games */
11651     SendToICS(ics_prefix);
11652     SendToICS("unobserve\n");
11653 }
11654
11655 void
11656 StopExaminingEvent()
11657 {
11658     /* Stop observing current game */
11659     SendToICS(ics_prefix);
11660     SendToICS("unexamine\n");
11661 }
11662
11663 void
11664 ForwardInner(target)
11665      int target;
11666 {
11667     int limit;
11668
11669     if (appData.debugMode)
11670         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11671                 target, currentMove, forwardMostMove);
11672
11673     if (gameMode == EditPosition)
11674       return;
11675
11676     if (gameMode == PlayFromGameFile && !pausing)
11677       PauseEvent();
11678
11679     if (gameMode == IcsExamining && pausing)
11680       limit = pauseExamForwardMostMove;
11681     else
11682       limit = forwardMostMove;
11683
11684     if (target > limit) target = limit;
11685
11686     if (target > 0 && moveList[target - 1][0]) {
11687         int fromX, fromY, toX, toY;
11688         toX = moveList[target - 1][2] - AAA;
11689         toY = moveList[target - 1][3] - ONE;
11690         if (moveList[target - 1][1] == '@') {
11691             if (appData.highlightLastMove) {
11692                 SetHighlights(-1, -1, toX, toY);
11693             }
11694         } else {
11695             fromX = moveList[target - 1][0] - AAA;
11696             fromY = moveList[target - 1][1] - ONE;
11697             if (target == currentMove + 1) {
11698                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11699             }
11700             if (appData.highlightLastMove) {
11701                 SetHighlights(fromX, fromY, toX, toY);
11702             }
11703         }
11704     }
11705     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11706         gameMode == Training || gameMode == PlayFromGameFile ||
11707         gameMode == AnalyzeFile) {
11708         while (currentMove < target) {
11709             SendMoveToProgram(currentMove++, &first);
11710         }
11711     } else {
11712         currentMove = target;
11713     }
11714
11715     if (gameMode == EditGame || gameMode == EndOfGame) {
11716         whiteTimeRemaining = timeRemaining[0][currentMove];
11717         blackTimeRemaining = timeRemaining[1][currentMove];
11718     }
11719     DisplayBothClocks();
11720     DisplayMove(currentMove - 1);
11721     DrawPosition(FALSE, boards[currentMove]);
11722     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11723     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11724         DisplayComment(currentMove - 1, commentList[currentMove]);
11725     }
11726 }
11727
11728
11729 void
11730 ForwardEvent()
11731 {
11732     if (gameMode == IcsExamining && !pausing) {
11733         SendToICS(ics_prefix);
11734         SendToICS("forward\n");
11735     } else {
11736         ForwardInner(currentMove + 1);
11737     }
11738 }
11739
11740 void
11741 ToEndEvent()
11742 {
11743     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11744         /* to optimze, we temporarily turn off analysis mode while we feed
11745          * the remaining moves to the engine. Otherwise we get analysis output
11746          * after each move.
11747          */
11748         if (first.analysisSupport) {
11749           SendToProgram("exit\nforce\n", &first);
11750           first.analyzing = FALSE;
11751         }
11752     }
11753
11754     if (gameMode == IcsExamining && !pausing) {
11755         SendToICS(ics_prefix);
11756         SendToICS("forward 999999\n");
11757     } else {
11758         ForwardInner(forwardMostMove);
11759     }
11760
11761     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11762         /* we have fed all the moves, so reactivate analysis mode */
11763         SendToProgram("analyze\n", &first);
11764         first.analyzing = TRUE;
11765         /*first.maybeThinking = TRUE;*/
11766         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11767     }
11768 }
11769
11770 void
11771 BackwardInner(target)
11772      int target;
11773 {
11774     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11775
11776     if (appData.debugMode)
11777         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11778                 target, currentMove, forwardMostMove);
11779
11780     if (gameMode == EditPosition) return;
11781     if (currentMove <= backwardMostMove) {
11782         ClearHighlights();
11783         DrawPosition(full_redraw, boards[currentMove]);
11784         return;
11785     }
11786     if (gameMode == PlayFromGameFile && !pausing)
11787       PauseEvent();
11788
11789     if (moveList[target][0]) {
11790         int fromX, fromY, toX, toY;
11791         toX = moveList[target][2] - AAA;
11792         toY = moveList[target][3] - ONE;
11793         if (moveList[target][1] == '@') {
11794             if (appData.highlightLastMove) {
11795                 SetHighlights(-1, -1, toX, toY);
11796             }
11797         } else {
11798             fromX = moveList[target][0] - AAA;
11799             fromY = moveList[target][1] - ONE;
11800             if (target == currentMove - 1) {
11801                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11802             }
11803             if (appData.highlightLastMove) {
11804                 SetHighlights(fromX, fromY, toX, toY);
11805             }
11806         }
11807     }
11808     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11809         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11810         while (currentMove > target) {
11811             SendToProgram("undo\n", &first);
11812             currentMove--;
11813         }
11814     } else {
11815         currentMove = target;
11816     }
11817
11818     if (gameMode == EditGame || gameMode == EndOfGame) {
11819         whiteTimeRemaining = timeRemaining[0][currentMove];
11820         blackTimeRemaining = timeRemaining[1][currentMove];
11821     }
11822     DisplayBothClocks();
11823     DisplayMove(currentMove - 1);
11824     DrawPosition(full_redraw, boards[currentMove]);
11825     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11826     // [HGM] PV info: routine tests if comment empty
11827     DisplayComment(currentMove - 1, commentList[currentMove]);
11828 }
11829
11830 void
11831 BackwardEvent()
11832 {
11833     if (gameMode == IcsExamining && !pausing) {
11834         SendToICS(ics_prefix);
11835         SendToICS("backward\n");
11836     } else {
11837         BackwardInner(currentMove - 1);
11838     }
11839 }
11840
11841 void
11842 ToStartEvent()
11843 {
11844     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11845         /* to optimize, we temporarily turn off analysis mode while we undo
11846          * all the moves. Otherwise we get analysis output after each undo.
11847          */
11848         if (first.analysisSupport) {
11849           SendToProgram("exit\nforce\n", &first);
11850           first.analyzing = FALSE;
11851         }
11852     }
11853
11854     if (gameMode == IcsExamining && !pausing) {
11855         SendToICS(ics_prefix);
11856         SendToICS("backward 999999\n");
11857     } else {
11858         BackwardInner(backwardMostMove);
11859     }
11860
11861     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11862         /* we have fed all the moves, so reactivate analysis mode */
11863         SendToProgram("analyze\n", &first);
11864         first.analyzing = TRUE;
11865         /*first.maybeThinking = TRUE;*/
11866         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11867     }
11868 }
11869
11870 void
11871 ToNrEvent(int to)
11872 {
11873   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11874   if (to >= forwardMostMove) to = forwardMostMove;
11875   if (to <= backwardMostMove) to = backwardMostMove;
11876   if (to < currentMove) {
11877     BackwardInner(to);
11878   } else {
11879     ForwardInner(to);
11880   }
11881 }
11882
11883 void
11884 RevertEvent()
11885 {
11886     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11887         return;
11888     }
11889     if (gameMode != IcsExamining) {
11890         DisplayError(_("You are not examining a game"), 0);
11891         return;
11892     }
11893     if (pausing) {
11894         DisplayError(_("You can't revert while pausing"), 0);
11895         return;
11896     }
11897     SendToICS(ics_prefix);
11898     SendToICS("revert\n");
11899 }
11900
11901 void
11902 RetractMoveEvent()
11903 {
11904     switch (gameMode) {
11905       case MachinePlaysWhite:
11906       case MachinePlaysBlack:
11907         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11908             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11909             return;
11910         }
11911         if (forwardMostMove < 2) return;
11912         currentMove = forwardMostMove = forwardMostMove - 2;
11913         whiteTimeRemaining = timeRemaining[0][currentMove];
11914         blackTimeRemaining = timeRemaining[1][currentMove];
11915         DisplayBothClocks();
11916         DisplayMove(currentMove - 1);
11917         ClearHighlights();/*!! could figure this out*/
11918         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11919         SendToProgram("remove\n", &first);
11920         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11921         break;
11922
11923       case BeginningOfGame:
11924       default:
11925         break;
11926
11927       case IcsPlayingWhite:
11928       case IcsPlayingBlack:
11929         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11930             SendToICS(ics_prefix);
11931             SendToICS("takeback 2\n");
11932         } else {
11933             SendToICS(ics_prefix);
11934             SendToICS("takeback 1\n");
11935         }
11936         break;
11937     }
11938 }
11939
11940 void
11941 MoveNowEvent()
11942 {
11943     ChessProgramState *cps;
11944
11945     switch (gameMode) {
11946       case MachinePlaysWhite:
11947         if (!WhiteOnMove(forwardMostMove)) {
11948             DisplayError(_("It is your turn"), 0);
11949             return;
11950         }
11951         cps = &first;
11952         break;
11953       case MachinePlaysBlack:
11954         if (WhiteOnMove(forwardMostMove)) {
11955             DisplayError(_("It is your turn"), 0);
11956             return;
11957         }
11958         cps = &first;
11959         break;
11960       case TwoMachinesPlay:
11961         if (WhiteOnMove(forwardMostMove) ==
11962             (first.twoMachinesColor[0] == 'w')) {
11963             cps = &first;
11964         } else {
11965             cps = &second;
11966         }
11967         break;
11968       case BeginningOfGame:
11969       default:
11970         return;
11971     }
11972     SendToProgram("?\n", cps);
11973 }
11974
11975 void
11976 TruncateGameEvent()
11977 {
11978     EditGameEvent();
11979     if (gameMode != EditGame) return;
11980     TruncateGame();
11981 }
11982
11983 void
11984 TruncateGame()
11985 {
11986     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
11987     if (forwardMostMove > currentMove) {
11988         if (gameInfo.resultDetails != NULL) {
11989             free(gameInfo.resultDetails);
11990             gameInfo.resultDetails = NULL;
11991             gameInfo.result = GameUnfinished;
11992         }
11993         forwardMostMove = currentMove;
11994         HistorySet(parseList, backwardMostMove, forwardMostMove,
11995                    currentMove-1);
11996     }
11997 }
11998
11999 void
12000 HintEvent()
12001 {
12002     if (appData.noChessProgram) return;
12003     switch (gameMode) {
12004       case MachinePlaysWhite:
12005         if (WhiteOnMove(forwardMostMove)) {
12006             DisplayError(_("Wait until your turn"), 0);
12007             return;
12008         }
12009         break;
12010       case BeginningOfGame:
12011       case MachinePlaysBlack:
12012         if (!WhiteOnMove(forwardMostMove)) {
12013             DisplayError(_("Wait until your turn"), 0);
12014             return;
12015         }
12016         break;
12017       default:
12018         DisplayError(_("No hint available"), 0);
12019         return;
12020     }
12021     SendToProgram("hint\n", &first);
12022     hintRequested = TRUE;
12023 }
12024
12025 void
12026 BookEvent()
12027 {
12028     if (appData.noChessProgram) return;
12029     switch (gameMode) {
12030       case MachinePlaysWhite:
12031         if (WhiteOnMove(forwardMostMove)) {
12032             DisplayError(_("Wait until your turn"), 0);
12033             return;
12034         }
12035         break;
12036       case BeginningOfGame:
12037       case MachinePlaysBlack:
12038         if (!WhiteOnMove(forwardMostMove)) {
12039             DisplayError(_("Wait until your turn"), 0);
12040             return;
12041         }
12042         break;
12043       case EditPosition:
12044         EditPositionDone(TRUE);
12045         break;
12046       case TwoMachinesPlay:
12047         return;
12048       default:
12049         break;
12050     }
12051     SendToProgram("bk\n", &first);
12052     bookOutput[0] = NULLCHAR;
12053     bookRequested = TRUE;
12054 }
12055
12056 void
12057 AboutGameEvent()
12058 {
12059     char *tags = PGNTags(&gameInfo);
12060     TagsPopUp(tags, CmailMsg());
12061     free(tags);
12062 }
12063
12064 /* end button procedures */
12065
12066 void
12067 PrintPosition(fp, move)
12068      FILE *fp;
12069      int move;
12070 {
12071     int i, j;
12072
12073     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12074         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12075             char c = PieceToChar(boards[move][i][j]);
12076             fputc(c == 'x' ? '.' : c, fp);
12077             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12078         }
12079     }
12080     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12081       fprintf(fp, "white to play\n");
12082     else
12083       fprintf(fp, "black to play\n");
12084 }
12085
12086 void
12087 PrintOpponents(fp)
12088      FILE *fp;
12089 {
12090     if (gameInfo.white != NULL) {
12091         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12092     } else {
12093         fprintf(fp, "\n");
12094     }
12095 }
12096
12097 /* Find last component of program's own name, using some heuristics */
12098 void
12099 TidyProgramName(prog, host, buf)
12100      char *prog, *host, buf[MSG_SIZ];
12101 {
12102     char *p, *q;
12103     int local = (strcmp(host, "localhost") == 0);
12104     while (!local && (p = strchr(prog, ';')) != NULL) {
12105         p++;
12106         while (*p == ' ') p++;
12107         prog = p;
12108     }
12109     if (*prog == '"' || *prog == '\'') {
12110         q = strchr(prog + 1, *prog);
12111     } else {
12112         q = strchr(prog, ' ');
12113     }
12114     if (q == NULL) q = prog + strlen(prog);
12115     p = q;
12116     while (p >= prog && *p != '/' && *p != '\\') p--;
12117     p++;
12118     if(p == prog && *p == '"') p++;
12119     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12120     memcpy(buf, p, q - p);
12121     buf[q - p] = NULLCHAR;
12122     if (!local) {
12123         strcat(buf, "@");
12124         strcat(buf, host);
12125     }
12126 }
12127
12128 char *
12129 TimeControlTagValue()
12130 {
12131     char buf[MSG_SIZ];
12132     if (!appData.clockMode) {
12133         strcpy(buf, "-");
12134     } else if (movesPerSession > 0) {
12135         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12136     } else if (timeIncrement == 0) {
12137         sprintf(buf, "%ld", timeControl/1000);
12138     } else {
12139         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12140     }
12141     return StrSave(buf);
12142 }
12143
12144 void
12145 SetGameInfo()
12146 {
12147     /* This routine is used only for certain modes */
12148     VariantClass v = gameInfo.variant;
12149     ChessMove r = GameUnfinished;
12150     char *p = NULL;
12151
12152     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12153         r = gameInfo.result; 
12154         p = gameInfo.resultDetails; 
12155         gameInfo.resultDetails = NULL;
12156     }
12157     ClearGameInfo(&gameInfo);
12158     gameInfo.variant = v;
12159
12160     switch (gameMode) {
12161       case MachinePlaysWhite:
12162         gameInfo.event = StrSave( appData.pgnEventHeader );
12163         gameInfo.site = StrSave(HostName());
12164         gameInfo.date = PGNDate();
12165         gameInfo.round = StrSave("-");
12166         gameInfo.white = StrSave(first.tidy);
12167         gameInfo.black = StrSave(UserName());
12168         gameInfo.timeControl = TimeControlTagValue();
12169         break;
12170
12171       case MachinePlaysBlack:
12172         gameInfo.event = StrSave( appData.pgnEventHeader );
12173         gameInfo.site = StrSave(HostName());
12174         gameInfo.date = PGNDate();
12175         gameInfo.round = StrSave("-");
12176         gameInfo.white = StrSave(UserName());
12177         gameInfo.black = StrSave(first.tidy);
12178         gameInfo.timeControl = TimeControlTagValue();
12179         break;
12180
12181       case TwoMachinesPlay:
12182         gameInfo.event = StrSave( appData.pgnEventHeader );
12183         gameInfo.site = StrSave(HostName());
12184         gameInfo.date = PGNDate();
12185         if (matchGame > 0) {
12186             char buf[MSG_SIZ];
12187             sprintf(buf, "%d", matchGame);
12188             gameInfo.round = StrSave(buf);
12189         } else {
12190             gameInfo.round = StrSave("-");
12191         }
12192         if (first.twoMachinesColor[0] == 'w') {
12193             gameInfo.white = StrSave(first.tidy);
12194             gameInfo.black = StrSave(second.tidy);
12195         } else {
12196             gameInfo.white = StrSave(second.tidy);
12197             gameInfo.black = StrSave(first.tidy);
12198         }
12199         gameInfo.timeControl = TimeControlTagValue();
12200         break;
12201
12202       case EditGame:
12203         gameInfo.event = StrSave("Edited game");
12204         gameInfo.site = StrSave(HostName());
12205         gameInfo.date = PGNDate();
12206         gameInfo.round = StrSave("-");
12207         gameInfo.white = StrSave("-");
12208         gameInfo.black = StrSave("-");
12209         gameInfo.result = r;
12210         gameInfo.resultDetails = p;
12211         break;
12212
12213       case EditPosition:
12214         gameInfo.event = StrSave("Edited position");
12215         gameInfo.site = StrSave(HostName());
12216         gameInfo.date = PGNDate();
12217         gameInfo.round = StrSave("-");
12218         gameInfo.white = StrSave("-");
12219         gameInfo.black = StrSave("-");
12220         break;
12221
12222       case IcsPlayingWhite:
12223       case IcsPlayingBlack:
12224       case IcsObserving:
12225       case IcsExamining:
12226         break;
12227
12228       case PlayFromGameFile:
12229         gameInfo.event = StrSave("Game from non-PGN file");
12230         gameInfo.site = StrSave(HostName());
12231         gameInfo.date = PGNDate();
12232         gameInfo.round = StrSave("-");
12233         gameInfo.white = StrSave("?");
12234         gameInfo.black = StrSave("?");
12235         break;
12236
12237       default:
12238         break;
12239     }
12240 }
12241
12242 void
12243 ReplaceComment(index, text)
12244      int index;
12245      char *text;
12246 {
12247     int len;
12248
12249     while (*text == '\n') text++;
12250     len = strlen(text);
12251     while (len > 0 && text[len - 1] == '\n') len--;
12252
12253     if (commentList[index] != NULL)
12254       free(commentList[index]);
12255
12256     if (len == 0) {
12257         commentList[index] = NULL;
12258         return;
12259     }
12260   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12261       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12262       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12263     commentList[index] = (char *) malloc(len + 2);
12264     strncpy(commentList[index], text, len);
12265     commentList[index][len] = '\n';
12266     commentList[index][len + 1] = NULLCHAR;
12267   } else { 
12268     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12269     char *p;
12270     commentList[index] = (char *) malloc(len + 6);
12271     strcpy(commentList[index], "{\n");
12272     strncpy(commentList[index]+2, text, len);
12273     commentList[index][len+2] = NULLCHAR;
12274     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12275     strcat(commentList[index], "\n}\n");
12276   }
12277 }
12278
12279 void
12280 CrushCRs(text)
12281      char *text;
12282 {
12283   char *p = text;
12284   char *q = text;
12285   char ch;
12286
12287   do {
12288     ch = *p++;
12289     if (ch == '\r') continue;
12290     *q++ = ch;
12291   } while (ch != '\0');
12292 }
12293
12294 void
12295 AppendComment(index, text, addBraces)
12296      int index;
12297      char *text;
12298      Boolean addBraces; // [HGM] braces: tells if we should add {}
12299 {
12300     int oldlen, len;
12301     char *old;
12302
12303 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12304     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12305
12306     CrushCRs(text);
12307     while (*text == '\n') text++;
12308     len = strlen(text);
12309     while (len > 0 && text[len - 1] == '\n') len--;
12310
12311     if (len == 0) return;
12312
12313     if (commentList[index] != NULL) {
12314         old = commentList[index];
12315         oldlen = strlen(old);
12316         while(commentList[index][oldlen-1] ==  '\n')
12317           commentList[index][--oldlen] = NULLCHAR;
12318         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12319         strcpy(commentList[index], old);
12320         free(old);
12321         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12322         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12323           if(addBraces) addBraces = FALSE; else { text++; len--; }
12324           while (*text == '\n') { text++; len--; }
12325           commentList[index][--oldlen] = NULLCHAR;
12326       }
12327         if(addBraces) strcat(commentList[index], "\n{\n");
12328         else          strcat(commentList[index], "\n");
12329         strcat(commentList[index], text);
12330         if(addBraces) strcat(commentList[index], "\n}\n");
12331         else          strcat(commentList[index], "\n");
12332     } else {
12333         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12334         if(addBraces)
12335              strcpy(commentList[index], "{\n");
12336         else commentList[index][0] = NULLCHAR;
12337         strcat(commentList[index], text);
12338         strcat(commentList[index], "\n");
12339         if(addBraces) strcat(commentList[index], "}\n");
12340     }
12341 }
12342
12343 static char * FindStr( char * text, char * sub_text )
12344 {
12345     char * result = strstr( text, sub_text );
12346
12347     if( result != NULL ) {
12348         result += strlen( sub_text );
12349     }
12350
12351     return result;
12352 }
12353
12354 /* [AS] Try to extract PV info from PGN comment */
12355 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12356 char *GetInfoFromComment( int index, char * text )
12357 {
12358     char * sep = text;
12359
12360     if( text != NULL && index > 0 ) {
12361         int score = 0;
12362         int depth = 0;
12363         int time = -1, sec = 0, deci;
12364         char * s_eval = FindStr( text, "[%eval " );
12365         char * s_emt = FindStr( text, "[%emt " );
12366
12367         if( s_eval != NULL || s_emt != NULL ) {
12368             /* New style */
12369             char delim;
12370
12371             if( s_eval != NULL ) {
12372                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12373                     return text;
12374                 }
12375
12376                 if( delim != ']' ) {
12377                     return text;
12378                 }
12379             }
12380
12381             if( s_emt != NULL ) {
12382             }
12383                 return text;
12384         }
12385         else {
12386             /* We expect something like: [+|-]nnn.nn/dd */
12387             int score_lo = 0;
12388
12389             if(*text != '{') return text; // [HGM] braces: must be normal comment
12390
12391             sep = strchr( text, '/' );
12392             if( sep == NULL || sep < (text+4) ) {
12393                 return text;
12394             }
12395
12396             time = -1; sec = -1; deci = -1;
12397             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12398                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12399                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12400                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12401                 return text;
12402             }
12403
12404             if( score_lo < 0 || score_lo >= 100 ) {
12405                 return text;
12406             }
12407
12408             if(sec >= 0) time = 600*time + 10*sec; else
12409             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12410
12411             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12412
12413             /* [HGM] PV time: now locate end of PV info */
12414             while( *++sep >= '0' && *sep <= '9'); // strip depth
12415             if(time >= 0)
12416             while( *++sep >= '0' && *sep <= '9'); // strip time
12417             if(sec >= 0)
12418             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12419             if(deci >= 0)
12420             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12421             while(*sep == ' ') sep++;
12422         }
12423
12424         if( depth <= 0 ) {
12425             return text;
12426         }
12427
12428         if( time < 0 ) {
12429             time = -1;
12430         }
12431
12432         pvInfoList[index-1].depth = depth;
12433         pvInfoList[index-1].score = score;
12434         pvInfoList[index-1].time  = 10*time; // centi-sec
12435         if(*sep == '}') *sep = 0; else *--sep = '{';
12436     }
12437     return sep;
12438 }
12439
12440 void
12441 SendToProgram(message, cps)
12442      char *message;
12443      ChessProgramState *cps;
12444 {
12445     int count, outCount, error;
12446     char buf[MSG_SIZ];
12447
12448     if (cps->pr == NULL) return;
12449     Attention(cps);
12450
12451     if (appData.debugMode) {
12452         TimeMark now;
12453         GetTimeMark(&now);
12454         fprintf(debugFP, "%ld >%-6s: %s",
12455                 SubtractTimeMarks(&now, &programStartTime),
12456                 cps->which, message);
12457     }
12458
12459     count = strlen(message);
12460     outCount = OutputToProcess(cps->pr, message, count, &error);
12461     if (outCount < count && !exiting
12462                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12463         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12464         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12465             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12466                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12467                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12468             } else {
12469                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12470             }
12471             gameInfo.resultDetails = StrSave(buf);
12472         }
12473         DisplayFatalError(buf, error, 1);
12474     }
12475 }
12476
12477 void
12478 ReceiveFromProgram(isr, closure, message, count, error)
12479      InputSourceRef isr;
12480      VOIDSTAR closure;
12481      char *message;
12482      int count;
12483      int error;
12484 {
12485     char *end_str;
12486     char buf[MSG_SIZ];
12487     ChessProgramState *cps = (ChessProgramState *)closure;
12488
12489     if (isr != cps->isr) return; /* Killed intentionally */
12490     if (count <= 0) {
12491         if (count == 0) {
12492             sprintf(buf,
12493                     _("Error: %s chess program (%s) exited unexpectedly"),
12494                     cps->which, cps->program);
12495         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12496                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12497                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12498                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12499                 } else {
12500                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12501                 }
12502                 gameInfo.resultDetails = StrSave(buf);
12503             }
12504             RemoveInputSource(cps->isr);
12505             DisplayFatalError(buf, 0, 1);
12506         } else {
12507             sprintf(buf,
12508                     _("Error reading from %s chess program (%s)"),
12509                     cps->which, cps->program);
12510             RemoveInputSource(cps->isr);
12511
12512             /* [AS] Program is misbehaving badly... kill it */
12513             if( count == -2 ) {
12514                 DestroyChildProcess( cps->pr, 9 );
12515                 cps->pr = NoProc;
12516             }
12517
12518             DisplayFatalError(buf, error, 1);
12519         }
12520         return;
12521     }
12522
12523     if ((end_str = strchr(message, '\r')) != NULL)
12524       *end_str = NULLCHAR;
12525     if ((end_str = strchr(message, '\n')) != NULL)
12526       *end_str = NULLCHAR;
12527
12528     if (appData.debugMode) {
12529         TimeMark now; int print = 1;
12530         char *quote = ""; char c; int i;
12531
12532         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12533                 char start = message[0];
12534                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12535                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12536                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12537                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12538                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12539                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12540                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12541                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12542                         { quote = "# "; print = (appData.engineComments == 2); }
12543                 message[0] = start; // restore original message
12544         }
12545         if(print) {
12546                 GetTimeMark(&now);
12547                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12548                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12549                         quote,
12550                         message);
12551         }
12552     }
12553
12554     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12555     if (appData.icsEngineAnalyze) {
12556         if (strstr(message, "whisper") != NULL ||
12557              strstr(message, "kibitz") != NULL ||
12558             strstr(message, "tellics") != NULL) return;
12559     }
12560
12561     HandleMachineMove(message, cps);
12562 }
12563
12564
12565 void
12566 SendTimeControl(cps, mps, tc, inc, sd, st)
12567      ChessProgramState *cps;
12568      int mps, inc, sd, st;
12569      long tc;
12570 {
12571     char buf[MSG_SIZ];
12572     int seconds;
12573
12574     if( timeControl_2 > 0 ) {
12575         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12576             tc = timeControl_2;
12577         }
12578     }
12579     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12580     inc /= cps->timeOdds;
12581     st  /= cps->timeOdds;
12582
12583     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12584
12585     if (st > 0) {
12586       /* Set exact time per move, normally using st command */
12587       if (cps->stKludge) {
12588         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12589         seconds = st % 60;
12590         if (seconds == 0) {
12591           sprintf(buf, "level 1 %d\n", st/60);
12592         } else {
12593           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12594         }
12595       } else {
12596         sprintf(buf, "st %d\n", st);
12597       }
12598     } else {
12599       /* Set conventional or incremental time control, using level command */
12600       if (seconds == 0) {
12601         /* Note old gnuchess bug -- minutes:seconds used to not work.
12602            Fixed in later versions, but still avoid :seconds
12603            when seconds is 0. */
12604         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12605       } else {
12606         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12607                 seconds, inc/1000);
12608       }
12609     }
12610     SendToProgram(buf, cps);
12611
12612     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12613     /* Orthogonally, limit search to given depth */
12614     if (sd > 0) {
12615       if (cps->sdKludge) {
12616         sprintf(buf, "depth\n%d\n", sd);
12617       } else {
12618         sprintf(buf, "sd %d\n", sd);
12619       }
12620       SendToProgram(buf, cps);
12621     }
12622
12623     if(cps->nps > 0) { /* [HGM] nps */
12624         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12625         else {
12626                 sprintf(buf, "nps %d\n", cps->nps);
12627               SendToProgram(buf, cps);
12628         }
12629     }
12630 }
12631
12632 ChessProgramState *WhitePlayer()
12633 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12634 {
12635     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12636        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12637         return &second;
12638     return &first;
12639 }
12640
12641 void
12642 SendTimeRemaining(cps, machineWhite)
12643      ChessProgramState *cps;
12644      int /*boolean*/ machineWhite;
12645 {
12646     char message[MSG_SIZ];
12647     long time, otime;
12648
12649     /* Note: this routine must be called when the clocks are stopped
12650        or when they have *just* been set or switched; otherwise
12651        it will be off by the time since the current tick started.
12652     */
12653     if (machineWhite) {
12654         time = whiteTimeRemaining / 10;
12655         otime = blackTimeRemaining / 10;
12656     } else {
12657         time = blackTimeRemaining / 10;
12658         otime = whiteTimeRemaining / 10;
12659     }
12660     /* [HGM] translate opponent's time by time-odds factor */
12661     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12662     if (appData.debugMode) {
12663         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12664     }
12665
12666     if (time <= 0) time = 1;
12667     if (otime <= 0) otime = 1;
12668
12669     sprintf(message, "time %ld\n", time);
12670     SendToProgram(message, cps);
12671
12672     sprintf(message, "otim %ld\n", otime);
12673     SendToProgram(message, cps);
12674 }
12675
12676 int
12677 BoolFeature(p, name, loc, cps)
12678      char **p;
12679      char *name;
12680      int *loc;
12681      ChessProgramState *cps;
12682 {
12683   char buf[MSG_SIZ];
12684   int len = strlen(name);
12685   int val;
12686   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12687     (*p) += len + 1;
12688     sscanf(*p, "%d", &val);
12689     *loc = (val != 0);
12690     while (**p && **p != ' ') (*p)++;
12691     sprintf(buf, "accepted %s\n", name);
12692     SendToProgram(buf, cps);
12693     return TRUE;
12694   }
12695   return FALSE;
12696 }
12697
12698 int
12699 IntFeature(p, name, loc, cps)
12700      char **p;
12701      char *name;
12702      int *loc;
12703      ChessProgramState *cps;
12704 {
12705   char buf[MSG_SIZ];
12706   int len = strlen(name);
12707   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12708     (*p) += len + 1;
12709     sscanf(*p, "%d", loc);
12710     while (**p && **p != ' ') (*p)++;
12711     sprintf(buf, "accepted %s\n", name);
12712     SendToProgram(buf, cps);
12713     return TRUE;
12714   }
12715   return FALSE;
12716 }
12717
12718 int
12719 StringFeature(p, name, loc, cps)
12720      char **p;
12721      char *name;
12722      char loc[];
12723      ChessProgramState *cps;
12724 {
12725   char buf[MSG_SIZ];
12726   int len = strlen(name);
12727   if (strncmp((*p), name, len) == 0
12728       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12729     (*p) += len + 2;
12730     sscanf(*p, "%[^\"]", loc);
12731     while (**p && **p != '\"') (*p)++;
12732     if (**p == '\"') (*p)++;
12733     sprintf(buf, "accepted %s\n", name);
12734     SendToProgram(buf, cps);
12735     return TRUE;
12736   }
12737   return FALSE;
12738 }
12739
12740 int
12741 ParseOption(Option *opt, ChessProgramState *cps)
12742 // [HGM] options: process the string that defines an engine option, and determine
12743 // name, type, default value, and allowed value range
12744 {
12745         char *p, *q, buf[MSG_SIZ];
12746         int n, min = (-1)<<31, max = 1<<31, def;
12747
12748         if(p = strstr(opt->name, " -spin ")) {
12749             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12750             if(max < min) max = min; // enforce consistency
12751             if(def < min) def = min;
12752             if(def > max) def = max;
12753             opt->value = def;
12754             opt->min = min;
12755             opt->max = max;
12756             opt->type = Spin;
12757         } else if((p = strstr(opt->name, " -slider "))) {
12758             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12759             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12760             if(max < min) max = min; // enforce consistency
12761             if(def < min) def = min;
12762             if(def > max) def = max;
12763             opt->value = def;
12764             opt->min = min;
12765             opt->max = max;
12766             opt->type = Spin; // Slider;
12767         } else if((p = strstr(opt->name, " -string "))) {
12768             opt->textValue = p+9;
12769             opt->type = TextBox;
12770         } else if((p = strstr(opt->name, " -file "))) {
12771             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12772             opt->textValue = p+7;
12773             opt->type = TextBox; // FileName;
12774         } else if((p = strstr(opt->name, " -path "))) {
12775             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12776             opt->textValue = p+7;
12777             opt->type = TextBox; // PathName;
12778         } else if(p = strstr(opt->name, " -check ")) {
12779             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12780             opt->value = (def != 0);
12781             opt->type = CheckBox;
12782         } else if(p = strstr(opt->name, " -combo ")) {
12783             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12784             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12785             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12786             opt->value = n = 0;
12787             while(q = StrStr(q, " /// ")) {
12788                 n++; *q = 0;    // count choices, and null-terminate each of them
12789                 q += 5;
12790                 if(*q == '*') { // remember default, which is marked with * prefix
12791                     q++;
12792                     opt->value = n;
12793                 }
12794                 cps->comboList[cps->comboCnt++] = q;
12795             }
12796             cps->comboList[cps->comboCnt++] = NULL;
12797             opt->max = n + 1;
12798             opt->type = ComboBox;
12799         } else if(p = strstr(opt->name, " -button")) {
12800             opt->type = Button;
12801         } else if(p = strstr(opt->name, " -save")) {
12802             opt->type = SaveButton;
12803         } else return FALSE;
12804         *p = 0; // terminate option name
12805         // now look if the command-line options define a setting for this engine option.
12806         if(cps->optionSettings && cps->optionSettings[0])
12807             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12808         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12809                 sprintf(buf, "option %s", p);
12810                 if(p = strstr(buf, ",")) *p = 0;
12811                 strcat(buf, "\n");
12812                 SendToProgram(buf, cps);
12813         }
12814         return TRUE;
12815 }
12816
12817 void
12818 FeatureDone(cps, val)
12819      ChessProgramState* cps;
12820      int val;
12821 {
12822   DelayedEventCallback cb = GetDelayedEvent();
12823   if ((cb == InitBackEnd3 && cps == &first) ||
12824       (cb == TwoMachinesEventIfReady && cps == &second)) {
12825     CancelDelayedEvent();
12826     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12827   }
12828   cps->initDone = val;
12829 }
12830
12831 /* Parse feature command from engine */
12832 void
12833 ParseFeatures(args, cps)
12834      char* args;
12835      ChessProgramState *cps;
12836 {
12837   char *p = args;
12838   char *q;
12839   int val;
12840   char buf[MSG_SIZ];
12841
12842   for (;;) {
12843     while (*p == ' ') p++;
12844     if (*p == NULLCHAR) return;
12845
12846     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12847     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12848     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12849     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12850     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12851     if (BoolFeature(&p, "reuse", &val, cps)) {
12852       /* Engine can disable reuse, but can't enable it if user said no */
12853       if (!val) cps->reuse = FALSE;
12854       continue;
12855     }
12856     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12857     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12858       if (gameMode == TwoMachinesPlay) {
12859         DisplayTwoMachinesTitle();
12860       } else {
12861         DisplayTitle("");
12862       }
12863       continue;
12864     }
12865     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12866     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12867     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12868     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12869     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12870     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12871     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12872     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12873     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12874     if (IntFeature(&p, "done", &val, cps)) {
12875       FeatureDone(cps, val);
12876       continue;
12877     }
12878     /* Added by Tord: */
12879     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12880     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12881     /* End of additions by Tord */
12882
12883     /* [HGM] added features: */
12884     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12885     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12886     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12887     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12888     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12889     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12890     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12891         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12892             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12893             SendToProgram(buf, cps);
12894             continue;
12895         }
12896         if(cps->nrOptions >= MAX_OPTIONS) {
12897             cps->nrOptions--;
12898             sprintf(buf, "%s engine has too many options\n", cps->which);
12899             DisplayError(buf, 0);
12900         }
12901         continue;
12902     }
12903     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12904     /* End of additions by HGM */
12905
12906     /* unknown feature: complain and skip */
12907     q = p;
12908     while (*q && *q != '=') q++;
12909     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12910     SendToProgram(buf, cps);
12911     p = q;
12912     if (*p == '=') {
12913       p++;
12914       if (*p == '\"') {
12915         p++;
12916         while (*p && *p != '\"') p++;
12917         if (*p == '\"') p++;
12918       } else {
12919         while (*p && *p != ' ') p++;
12920       }
12921     }
12922   }
12923
12924 }
12925
12926 void
12927 PeriodicUpdatesEvent(newState)
12928      int newState;
12929 {
12930     if (newState == appData.periodicUpdates)
12931       return;
12932
12933     appData.periodicUpdates=newState;
12934
12935     /* Display type changes, so update it now */
12936 //    DisplayAnalysis();
12937
12938     /* Get the ball rolling again... */
12939     if (newState) {
12940         AnalysisPeriodicEvent(1);
12941         StartAnalysisClock();
12942     }
12943 }
12944
12945 void
12946 PonderNextMoveEvent(newState)
12947      int newState;
12948 {
12949     if (newState == appData.ponderNextMove) return;
12950     if (gameMode == EditPosition) EditPositionDone(TRUE);
12951     if (newState) {
12952         SendToProgram("hard\n", &first);
12953         if (gameMode == TwoMachinesPlay) {
12954             SendToProgram("hard\n", &second);
12955         }
12956     } else {
12957         SendToProgram("easy\n", &first);
12958         thinkOutput[0] = NULLCHAR;
12959         if (gameMode == TwoMachinesPlay) {
12960             SendToProgram("easy\n", &second);
12961         }
12962     }
12963     appData.ponderNextMove = newState;
12964 }
12965
12966 void
12967 NewSettingEvent(option, command, value)
12968      char *command;
12969      int option, value;
12970 {
12971     char buf[MSG_SIZ];
12972
12973     if (gameMode == EditPosition) EditPositionDone(TRUE);
12974     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12975     SendToProgram(buf, &first);
12976     if (gameMode == TwoMachinesPlay) {
12977         SendToProgram(buf, &second);
12978     }
12979 }
12980
12981 void
12982 ShowThinkingEvent()
12983 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12984 {
12985     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12986     int newState = appData.showThinking
12987         // [HGM] thinking: other features now need thinking output as well
12988         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12989
12990     if (oldState == newState) return;
12991     oldState = newState;
12992     if (gameMode == EditPosition) EditPositionDone(TRUE);
12993     if (oldState) {
12994         SendToProgram("post\n", &first);
12995         if (gameMode == TwoMachinesPlay) {
12996             SendToProgram("post\n", &second);
12997         }
12998     } else {
12999         SendToProgram("nopost\n", &first);
13000         thinkOutput[0] = NULLCHAR;
13001         if (gameMode == TwoMachinesPlay) {
13002             SendToProgram("nopost\n", &second);
13003         }
13004     }
13005 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13006 }
13007
13008 void
13009 AskQuestionEvent(title, question, replyPrefix, which)
13010      char *title; char *question; char *replyPrefix; char *which;
13011 {
13012   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13013   if (pr == NoProc) return;
13014   AskQuestion(title, question, replyPrefix, pr);
13015 }
13016
13017 void
13018 DisplayMove(moveNumber)
13019      int moveNumber;
13020 {
13021     char message[MSG_SIZ];
13022     char res[MSG_SIZ];
13023     char cpThinkOutput[MSG_SIZ];
13024
13025     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13026
13027     if (moveNumber == forwardMostMove - 1 ||
13028         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13029
13030         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13031
13032         if (strchr(cpThinkOutput, '\n')) {
13033             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13034         }
13035     } else {
13036         *cpThinkOutput = NULLCHAR;
13037     }
13038
13039     /* [AS] Hide thinking from human user */
13040     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13041         *cpThinkOutput = NULLCHAR;
13042         if( thinkOutput[0] != NULLCHAR ) {
13043             int i;
13044
13045             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13046                 cpThinkOutput[i] = '.';
13047             }
13048             cpThinkOutput[i] = NULLCHAR;
13049             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13050         }
13051     }
13052
13053     if (moveNumber == forwardMostMove - 1 &&
13054         gameInfo.resultDetails != NULL) {
13055         if (gameInfo.resultDetails[0] == NULLCHAR) {
13056             sprintf(res, " %s", PGNResult(gameInfo.result));
13057         } else {
13058             sprintf(res, " {%s} %s",
13059                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13060         }
13061     } else {
13062         res[0] = NULLCHAR;
13063     }
13064
13065     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13066         DisplayMessage(res, cpThinkOutput);
13067     } else {
13068         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13069                 WhiteOnMove(moveNumber) ? " " : ".. ",
13070                 parseList[moveNumber], res);
13071         DisplayMessage(message, cpThinkOutput);
13072     }
13073 }
13074
13075 void
13076 DisplayComment(moveNumber, text)
13077      int moveNumber;
13078      char *text;
13079 {
13080     char title[MSG_SIZ];
13081     char buf[8000]; // comment can be long!
13082     int score, depth;
13083     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13084       strcpy(title, "Comment");
13085     } else {
13086       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13087               WhiteOnMove(moveNumber) ? " " : ".. ",
13088               parseList[moveNumber]);
13089     }
13090     // [HGM] PV info: display PV info together with (or as) comment
13091     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13092       if(text == NULL) text = "";                                           
13093       score = pvInfoList[moveNumber].score;
13094       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13095               depth, (pvInfoList[moveNumber].time+50)/100, text);
13096       text = buf;
13097     }
13098     if (text != NULL && (appData.autoDisplayComment || commentUp))
13099       CommentPopUp(title, text);
13100 }
13101
13102 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13103  * might be busy thinking or pondering.  It can be omitted if your
13104  * gnuchess is configured to stop thinking immediately on any user
13105  * input.  However, that gnuchess feature depends on the FIONREAD
13106  * ioctl, which does not work properly on some flavors of Unix.
13107  */
13108 void
13109 Attention(cps)
13110      ChessProgramState *cps;
13111 {
13112 #if ATTENTION
13113     if (!cps->useSigint) return;
13114     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13115     switch (gameMode) {
13116       case MachinePlaysWhite:
13117       case MachinePlaysBlack:
13118       case TwoMachinesPlay:
13119       case IcsPlayingWhite:
13120       case IcsPlayingBlack:
13121       case AnalyzeMode:
13122       case AnalyzeFile:
13123         /* Skip if we know it isn't thinking */
13124         if (!cps->maybeThinking) return;
13125         if (appData.debugMode)
13126           fprintf(debugFP, "Interrupting %s\n", cps->which);
13127         InterruptChildProcess(cps->pr);
13128         cps->maybeThinking = FALSE;
13129         break;
13130       default:
13131         break;
13132     }
13133 #endif /*ATTENTION*/
13134 }
13135
13136 int
13137 CheckFlags()
13138 {
13139     if (whiteTimeRemaining <= 0) {
13140         if (!whiteFlag) {
13141             whiteFlag = TRUE;
13142             if (appData.icsActive) {
13143                 if (appData.autoCallFlag &&
13144                     gameMode == IcsPlayingBlack && !blackFlag) {
13145                   SendToICS(ics_prefix);
13146                   SendToICS("flag\n");
13147                 }
13148             } else {
13149                 if (blackFlag) {
13150                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13151                 } else {
13152                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13153                     if (appData.autoCallFlag) {
13154                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13155                         return TRUE;
13156                     }
13157                 }
13158             }
13159         }
13160     }
13161     if (blackTimeRemaining <= 0) {
13162         if (!blackFlag) {
13163             blackFlag = TRUE;
13164             if (appData.icsActive) {
13165                 if (appData.autoCallFlag &&
13166                     gameMode == IcsPlayingWhite && !whiteFlag) {
13167                   SendToICS(ics_prefix);
13168                   SendToICS("flag\n");
13169                 }
13170             } else {
13171                 if (whiteFlag) {
13172                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13173                 } else {
13174                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13175                     if (appData.autoCallFlag) {
13176                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13177                         return TRUE;
13178                     }
13179                 }
13180             }
13181         }
13182     }
13183     return FALSE;
13184 }
13185
13186 void
13187 CheckTimeControl()
13188 {
13189     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13190         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13191
13192     /*
13193      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13194      */
13195     if ( !WhiteOnMove(forwardMostMove) )
13196         /* White made time control */
13197         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13198         /* [HGM] time odds: correct new time quota for time odds! */
13199                                             / WhitePlayer()->timeOdds;
13200       else
13201         /* Black made time control */
13202         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13203                                             / WhitePlayer()->other->timeOdds;
13204 }
13205
13206 void
13207 DisplayBothClocks()
13208 {
13209     int wom = gameMode == EditPosition ?
13210       !blackPlaysFirst : WhiteOnMove(currentMove);
13211     DisplayWhiteClock(whiteTimeRemaining, wom);
13212     DisplayBlackClock(blackTimeRemaining, !wom);
13213 }
13214
13215
13216 /* Timekeeping seems to be a portability nightmare.  I think everyone
13217    has ftime(), but I'm really not sure, so I'm including some ifdefs
13218    to use other calls if you don't.  Clocks will be less accurate if
13219    you have neither ftime nor gettimeofday.
13220 */
13221
13222 /* VS 2008 requires the #include outside of the function */
13223 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13224 #include <sys/timeb.h>
13225 #endif
13226
13227 /* Get the current time as a TimeMark */
13228 void
13229 GetTimeMark(tm)
13230      TimeMark *tm;
13231 {
13232 #if HAVE_GETTIMEOFDAY
13233
13234     struct timeval timeVal;
13235     struct timezone timeZone;
13236
13237     gettimeofday(&timeVal, &timeZone);
13238     tm->sec = (long) timeVal.tv_sec;
13239     tm->ms = (int) (timeVal.tv_usec / 1000L);
13240
13241 #else /*!HAVE_GETTIMEOFDAY*/
13242 #if HAVE_FTIME
13243
13244 // include <sys/timeb.h> / moved to just above start of function
13245     struct timeb timeB;
13246
13247     ftime(&timeB);
13248     tm->sec = (long) timeB.time;
13249     tm->ms = (int) timeB.millitm;
13250
13251 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13252     tm->sec = (long) time(NULL);
13253     tm->ms = 0;
13254 #endif
13255 #endif
13256 }
13257
13258 /* Return the difference in milliseconds between two
13259    time marks.  We assume the difference will fit in a long!
13260 */
13261 long
13262 SubtractTimeMarks(tm2, tm1)
13263      TimeMark *tm2, *tm1;
13264 {
13265     return 1000L*(tm2->sec - tm1->sec) +
13266            (long) (tm2->ms - tm1->ms);
13267 }
13268
13269
13270 /*
13271  * Code to manage the game clocks.
13272  *
13273  * In tournament play, black starts the clock and then white makes a move.
13274  * We give the human user a slight advantage if he is playing white---the
13275  * clocks don't run until he makes his first move, so it takes zero time.
13276  * Also, we don't account for network lag, so we could get out of sync
13277  * with GNU Chess's clock -- but then, referees are always right.
13278  */
13279
13280 static TimeMark tickStartTM;
13281 static long intendedTickLength;
13282
13283 long
13284 NextTickLength(timeRemaining)
13285      long timeRemaining;
13286 {
13287     long nominalTickLength, nextTickLength;
13288
13289     if (timeRemaining > 0L && timeRemaining <= 10000L)
13290       nominalTickLength = 100L;
13291     else
13292       nominalTickLength = 1000L;
13293     nextTickLength = timeRemaining % nominalTickLength;
13294     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13295
13296     return nextTickLength;
13297 }
13298
13299 /* Adjust clock one minute up or down */
13300 void
13301 AdjustClock(Boolean which, int dir)
13302 {
13303     if(which) blackTimeRemaining += 60000*dir;
13304     else      whiteTimeRemaining += 60000*dir;
13305     DisplayBothClocks();
13306 }
13307
13308 /* Stop clocks and reset to a fresh time control */
13309 void
13310 ResetClocks()
13311 {
13312     (void) StopClockTimer();
13313     if (appData.icsActive) {
13314         whiteTimeRemaining = blackTimeRemaining = 0;
13315     } else if (searchTime) {
13316         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13317         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13318     } else { /* [HGM] correct new time quote for time odds */
13319         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13320         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13321     }
13322     if (whiteFlag || blackFlag) {
13323         DisplayTitle("");
13324         whiteFlag = blackFlag = FALSE;
13325     }
13326     DisplayBothClocks();
13327 }
13328
13329 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13330
13331 /* Decrement running clock by amount of time that has passed */
13332 void
13333 DecrementClocks()
13334 {
13335     long timeRemaining;
13336     long lastTickLength, fudge;
13337     TimeMark now;
13338
13339     if (!appData.clockMode) return;
13340     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13341
13342     GetTimeMark(&now);
13343
13344     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13345
13346     /* Fudge if we woke up a little too soon */
13347     fudge = intendedTickLength - lastTickLength;
13348     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13349
13350     if (WhiteOnMove(forwardMostMove)) {
13351         if(whiteNPS >= 0) lastTickLength = 0;
13352         timeRemaining = whiteTimeRemaining -= lastTickLength;
13353         DisplayWhiteClock(whiteTimeRemaining - fudge,
13354                           WhiteOnMove(currentMove));
13355     } else {
13356         if(blackNPS >= 0) lastTickLength = 0;
13357         timeRemaining = blackTimeRemaining -= lastTickLength;
13358         DisplayBlackClock(blackTimeRemaining - fudge,
13359                           !WhiteOnMove(currentMove));
13360     }
13361
13362     if (CheckFlags()) return;
13363
13364     tickStartTM = now;
13365     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13366     StartClockTimer(intendedTickLength);
13367
13368     /* if the time remaining has fallen below the alarm threshold, sound the
13369      * alarm. if the alarm has sounded and (due to a takeback or time control
13370      * with increment) the time remaining has increased to a level above the
13371      * threshold, reset the alarm so it can sound again.
13372      */
13373
13374     if (appData.icsActive && appData.icsAlarm) {
13375
13376         /* make sure we are dealing with the user's clock */
13377         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13378                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13379            )) return;
13380
13381         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13382             alarmSounded = FALSE;
13383         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13384             PlayAlarmSound();
13385             alarmSounded = TRUE;
13386         }
13387     }
13388 }
13389
13390
13391 /* A player has just moved, so stop the previously running
13392    clock and (if in clock mode) start the other one.
13393    We redisplay both clocks in case we're in ICS mode, because
13394    ICS gives us an update to both clocks after every move.
13395    Note that this routine is called *after* forwardMostMove
13396    is updated, so the last fractional tick must be subtracted
13397    from the color that is *not* on move now.
13398 */
13399 void
13400 SwitchClocks()
13401 {
13402     long lastTickLength;
13403     TimeMark now;
13404     int flagged = FALSE;
13405
13406     GetTimeMark(&now);
13407
13408     if (StopClockTimer() && appData.clockMode) {
13409         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13410         if (WhiteOnMove(forwardMostMove)) {
13411             if(blackNPS >= 0) lastTickLength = 0;
13412             blackTimeRemaining -= lastTickLength;
13413            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13414 //         if(pvInfoList[forwardMostMove-1].time == -1)
13415                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13416                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13417         } else {
13418            if(whiteNPS >= 0) lastTickLength = 0;
13419            whiteTimeRemaining -= lastTickLength;
13420            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13421 //         if(pvInfoList[forwardMostMove-1].time == -1)
13422                  pvInfoList[forwardMostMove-1].time =
13423                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13424         }
13425         flagged = CheckFlags();
13426     }
13427     CheckTimeControl();
13428
13429     if (flagged || !appData.clockMode) return;
13430
13431     switch (gameMode) {
13432       case MachinePlaysBlack:
13433       case MachinePlaysWhite:
13434       case BeginningOfGame:
13435         if (pausing) return;
13436         break;
13437
13438       case EditGame:
13439       case PlayFromGameFile:
13440       case IcsExamining:
13441         return;
13442
13443       default:
13444         break;
13445     }
13446
13447     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13448         if(WhiteOnMove(forwardMostMove))
13449              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13450         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13451     }
13452
13453     tickStartTM = now;
13454     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13455       whiteTimeRemaining : blackTimeRemaining);
13456     StartClockTimer(intendedTickLength);
13457 }
13458
13459
13460 /* Stop both clocks */
13461 void
13462 StopClocks()
13463 {
13464     long lastTickLength;
13465     TimeMark now;
13466
13467     if (!StopClockTimer()) return;
13468     if (!appData.clockMode) return;
13469
13470     GetTimeMark(&now);
13471
13472     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13473     if (WhiteOnMove(forwardMostMove)) {
13474         if(whiteNPS >= 0) lastTickLength = 0;
13475         whiteTimeRemaining -= lastTickLength;
13476         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13477     } else {
13478         if(blackNPS >= 0) lastTickLength = 0;
13479         blackTimeRemaining -= lastTickLength;
13480         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13481     }
13482     CheckFlags();
13483 }
13484
13485 /* Start clock of player on move.  Time may have been reset, so
13486    if clock is already running, stop and restart it. */
13487 void
13488 StartClocks()
13489 {
13490     (void) StopClockTimer(); /* in case it was running already */
13491     DisplayBothClocks();
13492     if (CheckFlags()) return;
13493
13494     if (!appData.clockMode) return;
13495     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13496
13497     GetTimeMark(&tickStartTM);
13498     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13499       whiteTimeRemaining : blackTimeRemaining);
13500
13501    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13502     whiteNPS = blackNPS = -1;
13503     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13504        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13505         whiteNPS = first.nps;
13506     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13507        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13508         blackNPS = first.nps;
13509     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13510         whiteNPS = second.nps;
13511     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13512         blackNPS = second.nps;
13513     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13514
13515     StartClockTimer(intendedTickLength);
13516 }
13517
13518 char *
13519 TimeString(ms)
13520      long ms;
13521 {
13522     long second, minute, hour, day;
13523     char *sign = "";
13524     static char buf[32];
13525
13526     if (ms > 0 && ms <= 9900) {
13527       /* convert milliseconds to tenths, rounding up */
13528       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13529
13530       sprintf(buf, " %03.1f ", tenths/10.0);
13531       return buf;
13532     }
13533
13534     /* convert milliseconds to seconds, rounding up */
13535     /* use floating point to avoid strangeness of integer division
13536        with negative dividends on many machines */
13537     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13538
13539     if (second < 0) {
13540         sign = "-";
13541         second = -second;
13542     }
13543
13544     day = second / (60 * 60 * 24);
13545     second = second % (60 * 60 * 24);
13546     hour = second / (60 * 60);
13547     second = second % (60 * 60);
13548     minute = second / 60;
13549     second = second % 60;
13550
13551     if (day > 0)
13552       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13553               sign, day, hour, minute, second);
13554     else if (hour > 0)
13555       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13556     else
13557       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13558
13559     return buf;
13560 }
13561
13562
13563 /*
13564  * This is necessary because some C libraries aren't ANSI C compliant yet.
13565  */
13566 char *
13567 StrStr(string, match)
13568      char *string, *match;
13569 {
13570     int i, length;
13571
13572     length = strlen(match);
13573
13574     for (i = strlen(string) - length; i >= 0; i--, string++)
13575       if (!strncmp(match, string, length))
13576         return string;
13577
13578     return NULL;
13579 }
13580
13581 char *
13582 StrCaseStr(string, match)
13583      char *string, *match;
13584 {
13585     int i, j, length;
13586
13587     length = strlen(match);
13588
13589     for (i = strlen(string) - length; i >= 0; i--, string++) {
13590         for (j = 0; j < length; j++) {
13591             if (ToLower(match[j]) != ToLower(string[j]))
13592               break;
13593         }
13594         if (j == length) return string;
13595     }
13596
13597     return NULL;
13598 }
13599
13600 #ifndef _amigados
13601 int
13602 StrCaseCmp(s1, s2)
13603      char *s1, *s2;
13604 {
13605     char c1, c2;
13606
13607     for (;;) {
13608         c1 = ToLower(*s1++);
13609         c2 = ToLower(*s2++);
13610         if (c1 > c2) return 1;
13611         if (c1 < c2) return -1;
13612         if (c1 == NULLCHAR) return 0;
13613     }
13614 }
13615
13616
13617 int
13618 ToLower(c)
13619      int c;
13620 {
13621     return isupper(c) ? tolower(c) : c;
13622 }
13623
13624
13625 int
13626 ToUpper(c)
13627      int c;
13628 {
13629     return islower(c) ? toupper(c) : c;
13630 }
13631 #endif /* !_amigados    */
13632
13633 char *
13634 StrSave(s)
13635      char *s;
13636 {
13637     char *ret;
13638
13639     if ((ret = (char *) malloc(strlen(s) + 1))) {
13640         strcpy(ret, s);
13641     }
13642     return ret;
13643 }
13644
13645 char *
13646 StrSavePtr(s, savePtr)
13647      char *s, **savePtr;
13648 {
13649     if (*savePtr) {
13650         free(*savePtr);
13651     }
13652     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13653         strcpy(*savePtr, s);
13654     }
13655     return(*savePtr);
13656 }
13657
13658 char *
13659 PGNDate()
13660 {
13661     time_t clock;
13662     struct tm *tm;
13663     char buf[MSG_SIZ];
13664
13665     clock = time((time_t *)NULL);
13666     tm = localtime(&clock);
13667     sprintf(buf, "%04d.%02d.%02d",
13668             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13669     return StrSave(buf);
13670 }
13671
13672
13673 char *
13674 PositionToFEN(move, overrideCastling)
13675      int move;
13676      char *overrideCastling;
13677 {
13678     int i, j, fromX, fromY, toX, toY;
13679     int whiteToPlay;
13680     char buf[128];
13681     char *p, *q;
13682     int emptycount;
13683     ChessSquare piece;
13684
13685     whiteToPlay = (gameMode == EditPosition) ?
13686       !blackPlaysFirst : (move % 2 == 0);
13687     p = buf;
13688
13689     /* Piece placement data */
13690     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13691         emptycount = 0;
13692         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13693             if (boards[move][i][j] == EmptySquare) {
13694                 emptycount++;
13695             } else { ChessSquare piece = boards[move][i][j];
13696                 if (emptycount > 0) {
13697                     if(emptycount<10) /* [HGM] can be >= 10 */
13698                         *p++ = '0' + emptycount;
13699                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13700                     emptycount = 0;
13701                 }
13702                 if(PieceToChar(piece) == '+') {
13703                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13704                     *p++ = '+';
13705                     piece = (ChessSquare)(DEMOTED piece);
13706                 }
13707                 *p++ = PieceToChar(piece);
13708                 if(p[-1] == '~') {
13709                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13710                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13711                     *p++ = '~';
13712                 }
13713             }
13714         }
13715         if (emptycount > 0) {
13716             if(emptycount<10) /* [HGM] can be >= 10 */
13717                 *p++ = '0' + emptycount;
13718             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13719             emptycount = 0;
13720         }
13721         *p++ = '/';
13722     }
13723     *(p - 1) = ' ';
13724
13725     /* [HGM] print Crazyhouse or Shogi holdings */
13726     if( gameInfo.holdingsWidth ) {
13727         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13728         q = p;
13729         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13730             piece = boards[move][i][BOARD_WIDTH-1];
13731             if( piece != EmptySquare )
13732               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13733                   *p++ = PieceToChar(piece);
13734         }
13735         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13736             piece = boards[move][BOARD_HEIGHT-i-1][0];
13737             if( piece != EmptySquare )
13738               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13739                   *p++ = PieceToChar(piece);
13740         }
13741
13742         if( q == p ) *p++ = '-';
13743         *p++ = ']';
13744         *p++ = ' ';
13745     }
13746
13747     /* Active color */
13748     *p++ = whiteToPlay ? 'w' : 'b';
13749     *p++ = ' ';
13750
13751   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13752     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13753   } else {
13754   if(nrCastlingRights) {
13755      q = p;
13756      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13757        /* [HGM] write directly from rights */
13758            if(boards[move][CASTLING][2] != NoRights &&
13759               boards[move][CASTLING][0] != NoRights   )
13760                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13761            if(boards[move][CASTLING][2] != NoRights &&
13762               boards[move][CASTLING][1] != NoRights   )
13763                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13764            if(boards[move][CASTLING][5] != NoRights &&
13765               boards[move][CASTLING][3] != NoRights   )
13766                 *p++ = boards[move][CASTLING][3] + AAA;
13767            if(boards[move][CASTLING][5] != NoRights &&
13768               boards[move][CASTLING][4] != NoRights   )
13769                 *p++ = boards[move][CASTLING][4] + AAA;
13770      } else {
13771
13772         /* [HGM] write true castling rights */
13773         if( nrCastlingRights == 6 ) {
13774             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13775                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13776             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13777                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13778             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13779                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13780             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13781                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13782         }
13783      }
13784      if (q == p) *p++ = '-'; /* No castling rights */
13785      *p++ = ' ';
13786   }
13787
13788   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13789      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13790     /* En passant target square */
13791     if (move > backwardMostMove) {
13792         fromX = moveList[move - 1][0] - AAA;
13793         fromY = moveList[move - 1][1] - ONE;
13794         toX = moveList[move - 1][2] - AAA;
13795         toY = moveList[move - 1][3] - ONE;
13796         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13797             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13798             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13799             fromX == toX) {
13800             /* 2-square pawn move just happened */
13801             *p++ = toX + AAA;
13802             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13803         } else {
13804             *p++ = '-';
13805         }
13806     } else if(move == backwardMostMove) {
13807         // [HGM] perhaps we should always do it like this, and forget the above?
13808         if((signed char)boards[move][EP_STATUS] >= 0) {
13809             *p++ = boards[move][EP_STATUS] + AAA;
13810             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13811         } else {
13812             *p++ = '-';
13813         }
13814     } else {
13815         *p++ = '-';
13816     }
13817     *p++ = ' ';
13818   }
13819   }
13820
13821     /* [HGM] find reversible plies */
13822     {   int i = 0, j=move;
13823
13824         if (appData.debugMode) { int k;
13825             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13826             for(k=backwardMostMove; k<=forwardMostMove; k++)
13827                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13828
13829         }
13830
13831         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13832         if( j == backwardMostMove ) i += initialRulePlies;
13833         sprintf(p, "%d ", i);
13834         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13835     }
13836     /* Fullmove number */
13837     sprintf(p, "%d", (move / 2) + 1);
13838
13839     return StrSave(buf);
13840 }
13841
13842 Boolean
13843 ParseFEN(board, blackPlaysFirst, fen)
13844     Board board;
13845      int *blackPlaysFirst;
13846      char *fen;
13847 {
13848     int i, j;
13849     char *p;
13850     int emptycount;
13851     ChessSquare piece;
13852
13853     p = fen;
13854
13855     /* [HGM] by default clear Crazyhouse holdings, if present */
13856     if(gameInfo.holdingsWidth) {
13857        for(i=0; i<BOARD_HEIGHT; i++) {
13858            board[i][0]             = EmptySquare; /* black holdings */
13859            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13860            board[i][1]             = (ChessSquare) 0; /* black counts */
13861            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13862        }
13863     }
13864
13865     /* Piece placement data */
13866     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13867         j = 0;
13868         for (;;) {
13869             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13870                 if (*p == '/') p++;
13871                 emptycount = gameInfo.boardWidth - j;
13872                 while (emptycount--)
13873                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13874                 break;
13875 #if(BOARD_FILES >= 10)
13876             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13877                 p++; emptycount=10;
13878                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13879                 while (emptycount--)
13880                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13881 #endif
13882             } else if (isdigit(*p)) {
13883                 emptycount = *p++ - '0';
13884                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13885                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13886                 while (emptycount--)
13887                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13888             } else if (*p == '+' || isalpha(*p)) {
13889                 if (j >= gameInfo.boardWidth) return FALSE;
13890                 if(*p=='+') {
13891                     piece = CharToPiece(*++p);
13892                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13893                     piece = (ChessSquare) (PROMOTED piece ); p++;
13894                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13895                 } else piece = CharToPiece(*p++);
13896
13897                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13898                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13899                     piece = (ChessSquare) (PROMOTED piece);
13900                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13901                     p++;
13902                 }
13903                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13904             } else {
13905                 return FALSE;
13906             }
13907         }
13908     }
13909     while (*p == '/' || *p == ' ') p++;
13910
13911     /* [HGM] look for Crazyhouse holdings here */
13912     while(*p==' ') p++;
13913     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13914         if(*p == '[') p++;
13915         if(*p == '-' ) *p++; /* empty holdings */ else {
13916             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13917             /* if we would allow FEN reading to set board size, we would   */
13918             /* have to add holdings and shift the board read so far here   */
13919             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13920                 *p++;
13921                 if((int) piece >= (int) BlackPawn ) {
13922                     i = (int)piece - (int)BlackPawn;
13923                     i = PieceToNumber((ChessSquare)i);
13924                     if( i >= gameInfo.holdingsSize ) return FALSE;
13925                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13926                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13927                 } else {
13928                     i = (int)piece - (int)WhitePawn;
13929                     i = PieceToNumber((ChessSquare)i);
13930                     if( i >= gameInfo.holdingsSize ) return FALSE;
13931                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13932                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13933                 }
13934             }
13935         }
13936         if(*p == ']') *p++;
13937     }
13938
13939     while(*p == ' ') p++;
13940
13941     /* Active color */
13942     switch (*p++) {
13943       case 'w':
13944         *blackPlaysFirst = FALSE;
13945         break;
13946       case 'b':
13947         *blackPlaysFirst = TRUE;
13948         break;
13949       default:
13950         return FALSE;
13951     }
13952
13953     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13954     /* return the extra info in global variiables             */
13955
13956     /* set defaults in case FEN is incomplete */
13957     board[EP_STATUS] = EP_UNKNOWN;
13958     for(i=0; i<nrCastlingRights; i++ ) {
13959         board[CASTLING][i] =
13960             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13961     }   /* assume possible unless obviously impossible */
13962     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13963     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13964     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13965     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13966     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13967     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13968     FENrulePlies = 0;
13969
13970     while(*p==' ') p++;
13971     if(nrCastlingRights) {
13972       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13973           /* castling indicator present, so default becomes no castlings */
13974           for(i=0; i<nrCastlingRights; i++ ) {
13975                  board[CASTLING][i] = NoRights;
13976           }
13977       }
13978       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13979              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13980              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13981              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13982         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13983
13984         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13985             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13986             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13987         }
13988         switch(c) {
13989           case'K':
13990               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13991               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
13992               board[CASTLING][2] = whiteKingFile;
13993               break;
13994           case'Q':
13995               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13996               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
13997               board[CASTLING][2] = whiteKingFile;
13998               break;
13999           case'k':
14000               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14001               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14002               board[CASTLING][5] = blackKingFile;
14003               break;
14004           case'q':
14005               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14006               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14007               board[CASTLING][5] = blackKingFile;
14008           case '-':
14009               break;
14010           default: /* FRC castlings */
14011               if(c >= 'a') { /* black rights */
14012                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14013                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14014                   if(i == BOARD_RGHT) break;
14015                   board[CASTLING][5] = i;
14016                   c -= AAA;
14017                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14018                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14019                   if(c > i)
14020                       board[CASTLING][3] = c;
14021                   else
14022                       board[CASTLING][4] = c;
14023               } else { /* white rights */
14024                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14025                     if(board[0][i] == WhiteKing) break;
14026                   if(i == BOARD_RGHT) break;
14027                   board[CASTLING][2] = i;
14028                   c -= AAA - 'a' + 'A';
14029                   if(board[0][c] >= WhiteKing) break;
14030                   if(c > i)
14031                       board[CASTLING][0] = c;
14032                   else
14033                       board[CASTLING][1] = c;
14034               }
14035         }
14036       }
14037     if (appData.debugMode) {
14038         fprintf(debugFP, "FEN castling rights:");
14039         for(i=0; i<nrCastlingRights; i++)
14040         fprintf(debugFP, " %d", board[CASTLING][i]);
14041         fprintf(debugFP, "\n");
14042     }
14043
14044       while(*p==' ') p++;
14045     }
14046
14047     /* read e.p. field in games that know e.p. capture */
14048     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14049        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14050       if(*p=='-') {
14051         p++; board[EP_STATUS] = EP_NONE;
14052       } else {
14053          char c = *p++ - AAA;
14054
14055          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14056          if(*p >= '0' && *p <='9') *p++;
14057          board[EP_STATUS] = c;
14058       }
14059     }
14060
14061
14062     if(sscanf(p, "%d", &i) == 1) {
14063         FENrulePlies = i; /* 50-move ply counter */
14064         /* (The move number is still ignored)    */
14065     }
14066
14067     return TRUE;
14068 }
14069
14070 void
14071 EditPositionPasteFEN(char *fen)
14072 {
14073   if (fen != NULL) {
14074     Board initial_position;
14075
14076     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14077       DisplayError(_("Bad FEN position in clipboard"), 0);
14078       return ;
14079     } else {
14080       int savedBlackPlaysFirst = blackPlaysFirst;
14081       EditPositionEvent();
14082       blackPlaysFirst = savedBlackPlaysFirst;
14083       CopyBoard(boards[0], initial_position);
14084       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14085       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14086       DisplayBothClocks();
14087       DrawPosition(FALSE, boards[currentMove]);
14088     }
14089   }
14090 }
14091
14092 static char cseq[12] = "\\   ";
14093
14094 Boolean set_cont_sequence(char *new_seq)
14095 {
14096     int len;
14097     Boolean ret;
14098
14099     // handle bad attempts to set the sequence
14100         if (!new_seq)
14101                 return 0; // acceptable error - no debug
14102
14103     len = strlen(new_seq);
14104     ret = (len > 0) && (len < sizeof(cseq));
14105     if (ret)
14106         strcpy(cseq, new_seq);
14107     else if (appData.debugMode)
14108         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14109     return ret;
14110 }
14111
14112 /*
14113     reformat a source message so words don't cross the width boundary.  internal
14114     newlines are not removed.  returns the wrapped size (no null character unless
14115     included in source message).  If dest is NULL, only calculate the size required
14116     for the dest buffer.  lp argument indicats line position upon entry, and it's
14117     passed back upon exit.
14118 */
14119 int wrap(char *dest, char *src, int count, int width, int *lp)
14120 {
14121     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14122
14123     cseq_len = strlen(cseq);
14124     old_line = line = *lp;
14125     ansi = len = clen = 0;
14126
14127     for (i=0; i < count; i++)
14128     {
14129         if (src[i] == '\033')
14130             ansi = 1;
14131
14132         // if we hit the width, back up
14133         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14134         {
14135             // store i & len in case the word is too long
14136             old_i = i, old_len = len;
14137
14138             // find the end of the last word
14139             while (i && src[i] != ' ' && src[i] != '\n')
14140             {
14141                 i--;
14142                 len--;
14143             }
14144
14145             // word too long?  restore i & len before splitting it
14146             if ((old_i-i+clen) >= width)
14147             {
14148                 i = old_i;
14149                 len = old_len;
14150             }
14151
14152             // extra space?
14153             if (i && src[i-1] == ' ')
14154                 len--;
14155
14156             if (src[i] != ' ' && src[i] != '\n')
14157             {
14158                 i--;
14159                 if (len)
14160                     len--;
14161             }
14162
14163             // now append the newline and continuation sequence
14164             if (dest)
14165                 dest[len] = '\n';
14166             len++;
14167             if (dest)
14168                 strncpy(dest+len, cseq, cseq_len);
14169             len += cseq_len;
14170             line = cseq_len;
14171             clen = cseq_len;
14172             continue;
14173         }
14174
14175         if (dest)
14176             dest[len] = src[i];
14177         len++;
14178         if (!ansi)
14179             line++;
14180         if (src[i] == '\n')
14181             line = 0;
14182         if (src[i] == 'm')
14183             ansi = 0;
14184     }
14185     if (dest && appData.debugMode)
14186     {
14187         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14188             count, width, line, len, *lp);
14189         show_bytes(debugFP, src, count);
14190         fprintf(debugFP, "\ndest: ");
14191         show_bytes(debugFP, dest, len);
14192         fprintf(debugFP, "\n");
14193     }
14194     *lp = dest ? line : old_line;
14195
14196     return len;
14197 }
14198
14199 // [HGM] vari: routines for shelving variations
14200
14201 void 
14202 PushTail(int firstMove, int lastMove)
14203 {
14204         int i, j, nrMoves = lastMove - firstMove;
14205
14206         if(appData.icsActive) { // only in local mode
14207                 forwardMostMove = currentMove; // mimic old ICS behavior
14208                 return;
14209         }
14210         if(storedGames >= MAX_VARIATIONS-1) return;
14211
14212         // push current tail of game on stack
14213         savedResult[storedGames] = gameInfo.result;
14214         savedDetails[storedGames] = gameInfo.resultDetails;
14215         gameInfo.resultDetails = NULL;
14216         savedFirst[storedGames] = firstMove;
14217         savedLast [storedGames] = lastMove;
14218         savedFramePtr[storedGames] = framePtr;
14219         framePtr -= nrMoves; // reserve space for the boards
14220         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14221             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14222             for(j=0; j<MOVE_LEN; j++)
14223                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14224             for(j=0; j<2*MOVE_LEN; j++)
14225                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14226             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14227             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14228             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14229             pvInfoList[firstMove+i-1].depth = 0;
14230             commentList[framePtr+i] = commentList[firstMove+i];
14231             commentList[firstMove+i] = NULL;
14232         }
14233
14234         storedGames++;
14235         forwardMostMove = currentMove; // truncte game so we can start variation
14236         if(storedGames == 1) GreyRevert(FALSE);
14237 }
14238
14239 Boolean
14240 PopTail(Boolean annotate)
14241 {
14242         int i, j, nrMoves;
14243         char buf[8000], moveBuf[20];
14244
14245         if(appData.icsActive) return FALSE; // only in local mode
14246         if(!storedGames) return FALSE; // sanity
14247
14248         storedGames--;
14249         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14250         nrMoves = savedLast[storedGames] - currentMove;
14251         if(annotate) {
14252                 int cnt = 10;
14253                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14254                 else strcpy(buf, "(");
14255                 for(i=currentMove; i<forwardMostMove; i++) {
14256                         if(WhiteOnMove(i))
14257                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14258                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14259                         strcat(buf, moveBuf);
14260                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14261                 }
14262                 strcat(buf, ")");
14263         }
14264         for(i=1; i<nrMoves; i++) { // copy last variation back
14265             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14266             for(j=0; j<MOVE_LEN; j++)
14267                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14268             for(j=0; j<2*MOVE_LEN; j++)
14269                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14270             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14271             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14272             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14273             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14274             commentList[currentMove+i] = commentList[framePtr+i];
14275             commentList[framePtr+i] = NULL;
14276         }
14277         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14278         framePtr = savedFramePtr[storedGames];
14279         gameInfo.result = savedResult[storedGames];
14280         if(gameInfo.resultDetails != NULL) {
14281             free(gameInfo.resultDetails);
14282       }
14283         gameInfo.resultDetails = savedDetails[storedGames];
14284         forwardMostMove = currentMove + nrMoves;
14285         if(storedGames == 0) GreyRevert(TRUE);
14286         return TRUE;
14287 }
14288
14289 void 
14290 CleanupTail()
14291 {       // remove all shelved variations
14292         int i;
14293         for(i=0; i<storedGames; i++) {
14294             if(savedDetails[i])
14295                 free(savedDetails[i]);
14296             savedDetails[i] = NULL;
14297         }
14298         for(i=framePtr; i<MAX_MOVES; i++) {
14299                 if(commentList[i]) free(commentList[i]);
14300                 commentList[i] = NULL;
14301         }
14302         framePtr = MAX_MOVES-1;
14303         storedGames = 0;
14304 }