fixed merge: messed up backend.c and forgot to add it before commiting
[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
5462   /* Relay move to ICS or chess engine */
5463   if (appData.icsActive)
5464     {
5465       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5466           gameMode == IcsExamining)
5467         {
5468           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5469           ics_user_moved = 1;
5470         }
5471     }
5472   else
5473     {
5474       if (first.sendTime && (gameMode == BeginningOfGame ||
5475                              gameMode == MachinePlaysWhite ||
5476                              gameMode == MachinePlaysBlack))
5477         {
5478           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5479         }
5480       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5481         {
5482           // [HGM] book: if program might be playing, let it use book
5483           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5484           first.maybeThinking = TRUE;
5485         }
5486       else
5487         SendMoveToProgram(forwardMostMove-1, &first);
5488       if (currentMove == cmailOldMove + 1)
5489         {
5490           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5491         }
5492     }
5493
5494   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5495
5496   switch (gameMode) 
5497     {
5498     case EditGame:
5499       switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) 
5500         {
5501         case MT_NONE:
5502         case MT_CHECK:
5503           break;
5504         case MT_CHECKMATE:
5505         case MT_STAINMATE:
5506           if (WhiteOnMove(currentMove)) {
5507             GameEnds(BlackWins, "Black mates", GE_PLAYER);
5508           } else {
5509             GameEnds(WhiteWins, "White mates", GE_PLAYER);
5510           }
5511           break;
5512         case MT_STALEMATE:
5513           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5514           break;
5515         }
5516       break;
5517       
5518     case MachinePlaysBlack:
5519     case MachinePlaysWhite:
5520       /* disable certain menu options while machine is thinking */
5521       SetMachineThinkingEnables();
5522       break;
5523       
5524     default:
5525       break;
5526     }
5527   
5528   if(bookHit)
5529     { // [HGM] book: simulate book reply
5530       static char bookMove[MSG_SIZ]; // a bit generous?
5531
5532       programStats.nodes = programStats.depth = programStats.time =
5533         programStats.score = programStats.got_only_move = 0;
5534       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5535
5536       strcpy(bookMove, "move ");
5537       strcat(bookMove, bookHit);
5538       HandleMachineMove(bookMove, &first);
5539     }
5540
5541   return 1;
5542 }
5543
5544 void
5545 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5546      int fromX, fromY, toX, toY;
5547      int promoChar;
5548 {
5549     /* [HGM] This routine was added to allow calling of its two logical
5550        parts from other modules in the old way. Before, UserMoveEvent()
5551        automatically called FinishMove() if the move was OK, and returned
5552        otherwise. I separated the two, in order to make it possible to
5553        slip a promotion popup in between. But that it always needs two
5554        calls, to the first part, (now called UserMoveTest() ), and to
5555        FinishMove if the first part succeeded. Calls that do not need
5556        to do anything in between, can call this routine the old way.
5557     */
5558   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5559   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5560   if(moveType == AmbiguousMove)
5561     DrawPosition(FALSE, boards[currentMove]);
5562   else if(moveType != ImpossibleMove && moveType != Comment)
5563     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5564 }
5565
5566 void LeftClick(ClickType clickType, int xPix, int yPix)
5567 {
5568     int x, y;
5569     Boolean saveAnimate;
5570     static int second = 0, promotionChoice = 0;
5571     char promoChoice = NULLCHAR;
5572
5573     if (clickType == Press) ErrorPopDown();
5574
5575     x = EventToSquare(xPix, BOARD_WIDTH);
5576     y = EventToSquare(yPix, BOARD_HEIGHT);
5577     if (!flipView && y >= 0) {
5578         y = BOARD_HEIGHT - 1 - y;
5579     }
5580     if (flipView && x >= 0) {
5581         x = BOARD_WIDTH - 1 - x;
5582     }
5583
5584     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5585         if(clickType == Release) return; // ignore upclick of click-click destination
5586         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5587         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5588         if(gameInfo.holdingsWidth && 
5589                 (WhiteOnMove(currentMove) 
5590                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5591                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5592             // click in right holdings, for determining promotion piece
5593             ChessSquare p = boards[currentMove][y][x];
5594             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5595             if(p != EmptySquare) {
5596                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5597                 fromX = fromY = -1;
5598                 return;
5599             }
5600         }
5601         DrawPosition(FALSE, boards[currentMove]);
5602         return;
5603     }
5604
5605     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5606     if(clickType == Press
5607             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5608               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5609               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5610         return;
5611
5612     if (fromX == -1) {
5613         if (clickType == Press) {
5614             /* First square */
5615             if (OKToStartUserMove(x, y)) {
5616                 fromX = x;
5617                 fromY = y;
5618                 second = 0;
5619                 DragPieceBegin(xPix, yPix);
5620                 if (appData.highlightDragging) {
5621                     SetHighlights(x, y, -1, -1);
5622                 }
5623             }
5624         }
5625         return;
5626     }
5627
5628     /* fromX != -1 */
5629     if (clickType == Press && gameMode != EditPosition) {
5630         ChessSquare fromP;
5631         ChessSquare toP;
5632         int frc;
5633
5634         // ignore off-board to clicks
5635         if(y < 0 || x < 0) return;
5636
5637         /* Check if clicking again on the same color piece */
5638         fromP = boards[currentMove][fromY][fromX];
5639         toP = boards[currentMove][y][x];
5640         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5641         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5642              WhitePawn <= toP && toP <= WhiteKing &&
5643              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5644              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5645             (BlackPawn <= fromP && fromP <= BlackKing && 
5646              BlackPawn <= toP && toP <= BlackKing &&
5647              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5648              !(fromP == BlackKing && toP == BlackRook && frc))) {
5649             /* Clicked again on same color piece -- changed his mind */
5650             second = (x == fromX && y == fromY);
5651             if (appData.highlightDragging) {
5652                 SetHighlights(x, y, -1, -1);
5653             } else {
5654                 ClearHighlights();
5655             }
5656             if (OKToStartUserMove(x, y)) {
5657                 fromX = x;
5658                 fromY = y;
5659                 DragPieceBegin(xPix, yPix);
5660             }
5661             return;
5662         }
5663         // ignore clicks on holdings
5664         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5665     }
5666
5667     if (clickType == Release && x == fromX && y == fromY) {
5668         DragPieceEnd(xPix, yPix);
5669         if (appData.animateDragging) {
5670             /* Undo animation damage if any */
5671             DrawPosition(FALSE, NULL);
5672         }
5673         if (second) {
5674             /* Second up/down in same square; just abort move */
5675             second = 0;
5676             fromX = fromY = -1;
5677             ClearHighlights();
5678             gotPremove = 0;
5679             ClearPremoveHighlights();
5680         } else {
5681             /* First upclick in same square; start click-click mode */
5682             SetHighlights(x, y, -1, -1);
5683         }
5684         return;
5685     }
5686
5687     /* we now have a different from- and (possibly off-board) to-square */
5688     /* Completed move */
5689     toX = x;
5690     toY = y;
5691     saveAnimate = appData.animate;
5692     if (clickType == Press) {
5693         /* Finish clickclick move */
5694         if (appData.animate || appData.highlightLastMove) {
5695             SetHighlights(fromX, fromY, toX, toY);
5696         } else {
5697             ClearHighlights();
5698         }
5699     } else {
5700         /* Finish drag move */
5701         if (appData.highlightLastMove) {
5702             SetHighlights(fromX, fromY, toX, toY);
5703         } else {
5704             ClearHighlights();
5705         }
5706         DragPieceEnd(xPix, yPix);
5707         /* Don't animate move and drag both */
5708         appData.animate = FALSE;
5709     }
5710
5711     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5712     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5713         ClearHighlights();
5714         fromX = fromY = -1;
5715         DrawPosition(TRUE, NULL);
5716         return;
5717     }
5718
5719     // off-board moves should not be highlighted
5720     if(x < 0 || x < 0) ClearHighlights();
5721
5722     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5723         SetHighlights(fromX, fromY, toX, toY);
5724         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5725             // [HGM] super: promotion to captured piece selected from holdings
5726             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5727             promotionChoice = TRUE;
5728             // kludge follows to temporarily execute move on display, without promoting yet
5729             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5730             boards[currentMove][toY][toX] = p;
5731             DrawPosition(FALSE, boards[currentMove]);
5732             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5733             boards[currentMove][toY][toX] = q;
5734             DisplayMessage("Click in holdings to choose piece", "");
5735             return;
5736         }
5737         PromotionPopUp();
5738     } else {
5739         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5740         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5741         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5742         fromX = fromY = -1;
5743     }
5744     appData.animate = saveAnimate;
5745     if (appData.animate || appData.animateDragging) {
5746         /* Undo animation damage if needed */
5747         DrawPosition(FALSE, NULL);
5748     }
5749 }
5750
5751 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5752 {
5753 //    char * hint = lastHint;
5754     FrontEndProgramStats stats;
5755
5756     stats.which = cps == &first ? 0 : 1;
5757     stats.depth = cpstats->depth;
5758     stats.nodes = cpstats->nodes;
5759     stats.score = cpstats->score;
5760     stats.time = cpstats->time;
5761     stats.pv = cpstats->movelist;
5762     stats.hint = lastHint;
5763     stats.an_move_index = 0;
5764     stats.an_move_count = 0;
5765
5766     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5767         stats.hint = cpstats->move_name;
5768         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5769         stats.an_move_count = cpstats->nr_moves;
5770     }
5771
5772     SetProgramStats( &stats );
5773 }
5774
5775 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5776 {   // [HGM] book: this routine intercepts moves to simulate book replies
5777     char *bookHit = NULL;
5778
5779     //first determine if the incoming move brings opponent into his book
5780     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5781         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5782     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5783     if(bookHit != NULL && !cps->bookSuspend) {
5784         // make sure opponent is not going to reply after receiving move to book position
5785         SendToProgram("force\n", cps);
5786         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5787     }
5788     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5789     // now arrange restart after book miss
5790     if(bookHit) {
5791         // after a book hit we never send 'go', and the code after the call to this routine
5792         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5793         char buf[MSG_SIZ];
5794         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5795         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5796         SendToProgram(buf, cps);
5797         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5798     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5799         SendToProgram("go\n", cps);
5800         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5801     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5802         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5803             SendToProgram("go\n", cps);
5804         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5805     }
5806     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5807 }
5808
5809 char *savedMessage;
5810 ChessProgramState *savedState;
5811 void DeferredBookMove(void)
5812 {
5813         if(savedState->lastPing != savedState->lastPong)
5814                     ScheduleDelayedEvent(DeferredBookMove, 10);
5815         else
5816         HandleMachineMove(savedMessage, savedState);
5817 }
5818
5819 void
5820 HandleMachineMove(message, cps)
5821      char *message;
5822      ChessProgramState *cps;
5823 {
5824     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5825     char realname[MSG_SIZ];
5826     int fromX, fromY, toX, toY;
5827     ChessMove moveType;
5828     char promoChar;
5829     char *p;
5830     int machineWhite;
5831     char *bookHit;
5832
5833 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5834     /*
5835      * Kludge to ignore BEL characters
5836      */
5837     while (*message == '\007') message++;
5838
5839     /*
5840      * [HGM] engine debug message: ignore lines starting with '#' character
5841      */
5842     if(cps->debug && *message == '#') return;
5843
5844     /*
5845      * Look for book output
5846      */
5847     if (cps == &first && bookRequested) {
5848         if (message[0] == '\t' || message[0] == ' ') {
5849             /* Part of the book output is here; append it */
5850             strcat(bookOutput, message);
5851             strcat(bookOutput, "  \n");
5852             return;
5853         } else if (bookOutput[0] != NULLCHAR) {
5854             /* All of book output has arrived; display it */
5855             char *p = bookOutput;
5856             while (*p != NULLCHAR) {
5857                 if (*p == '\t') *p = ' ';
5858                 p++;
5859             }
5860             DisplayInformation(bookOutput);
5861             bookRequested = FALSE;
5862             /* Fall through to parse the current output */
5863         }
5864     }
5865
5866     /*
5867      * Look for machine move.
5868      */
5869     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5870         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5871     {
5872         /* This method is only useful on engines that support ping */
5873         if (cps->lastPing != cps->lastPong) {
5874           if (gameMode == BeginningOfGame) {
5875             /* Extra move from before last new; ignore */
5876             if (appData.debugMode) {
5877                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5878             }
5879           } else {
5880             if (appData.debugMode) {
5881                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5882                         cps->which, gameMode);
5883             }
5884
5885             SendToProgram("undo\n", cps);
5886           }
5887           return;
5888         }
5889
5890         switch (gameMode) {
5891           case BeginningOfGame:
5892             /* Extra move from before last reset; ignore */
5893             if (appData.debugMode) {
5894                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5895             }
5896             return;
5897
5898           case EndOfGame:
5899           case IcsIdle:
5900           default:
5901             /* Extra move after we tried to stop.  The mode test is
5902                not a reliable way of detecting this problem, but it's
5903                the best we can do on engines that don't support ping.
5904             */
5905             if (appData.debugMode) {
5906                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5907                         cps->which, gameMode);
5908             }
5909             SendToProgram("undo\n", cps);
5910             return;
5911
5912           case MachinePlaysWhite:
5913           case IcsPlayingWhite:
5914             machineWhite = TRUE;
5915             break;
5916
5917           case MachinePlaysBlack:
5918           case IcsPlayingBlack:
5919             machineWhite = FALSE;
5920             break;
5921
5922           case TwoMachinesPlay:
5923             machineWhite = (cps->twoMachinesColor[0] == 'w');
5924             break;
5925         }
5926         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5927             if (appData.debugMode) {
5928                 fprintf(debugFP,
5929                         "Ignoring move out of turn by %s, gameMode %d"
5930                         ", forwardMost %d\n",
5931                         cps->which, gameMode, forwardMostMove);
5932             }
5933             return;
5934         }
5935
5936     if (appData.debugMode) { int f = forwardMostMove;
5937         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5938                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5939                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5940     }
5941         if(cps->alphaRank) AlphaRank(machineMove, 4);
5942         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5943                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5944             /* Machine move could not be parsed; ignore it. */
5945             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5946                     machineMove, cps->which);
5947             DisplayError(buf1, 0);
5948             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5949                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5950             if (gameMode == TwoMachinesPlay) {
5951               GameEnds(machineWhite ? BlackWins : WhiteWins,
5952                        buf1, GE_XBOARD);
5953             }
5954             return;
5955         }
5956
5957         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5958         /* So we have to redo legality test with true e.p. status here,  */
5959         /* to make sure an illegal e.p. capture does not slip through,   */
5960         /* to cause a forfeit on a justified illegal-move complaint      */
5961         /* of the opponent.                                              */
5962         if( gameMode==TwoMachinesPlay && appData.testLegality
5963             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5964                                                               ) {
5965            ChessMove moveType;
5966            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5967                              fromY, fromX, toY, toX, promoChar);
5968             if (appData.debugMode) {
5969                 int i;
5970                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5971                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5972                 fprintf(debugFP, "castling rights\n");
5973             }
5974             if(moveType == IllegalMove) {
5975                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5976                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5977                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5978                            buf1, GE_XBOARD);
5979                 return;
5980            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5981            /* [HGM] Kludge to handle engines that send FRC-style castling
5982               when they shouldn't (like TSCP-Gothic) */
5983            switch(moveType) {
5984              case WhiteASideCastleFR:
5985              case BlackASideCastleFR:
5986                toX+=2;
5987                currentMoveString[2]++;
5988                break;
5989              case WhiteHSideCastleFR:
5990              case BlackHSideCastleFR:
5991                toX--;
5992                currentMoveString[2]--;
5993                break;
5994              default: ; // nothing to do, but suppresses warning of pedantic compilers
5995            }
5996         }
5997         hintRequested = FALSE;
5998         lastHint[0] = NULLCHAR;
5999         bookRequested = FALSE;
6000         /* Program may be pondering now */
6001         cps->maybeThinking = TRUE;
6002         if (cps->sendTime == 2) cps->sendTime = 1;
6003         if (cps->offeredDraw) cps->offeredDraw--;
6004
6005 #if ZIPPY
6006         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6007             first.initDone) {
6008           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6009           ics_user_moved = 1;
6010           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6011                 char buf[3*MSG_SIZ];
6012
6013                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6014                         programStats.score / 100.,
6015                         programStats.depth,
6016                         programStats.time / 100.,
6017                         (unsigned int)programStats.nodes,
6018                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6019                         programStats.movelist);
6020                 SendToICS(buf);
6021 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6022           }
6023         }
6024 #endif
6025         /* currentMoveString is set as a side-effect of ParseOneMove */
6026         strcpy(machineMove, currentMoveString);
6027         strcat(machineMove, "\n");
6028         strcpy(moveList[forwardMostMove], machineMove);
6029
6030         /* [AS] Save move info and clear stats for next move */
6031         pvInfoList[ forwardMostMove ].score = programStats.score;
6032         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6033         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6034         ClearProgramStats();
6035         thinkOutput[0] = NULLCHAR;
6036         hiddenThinkOutputState = 0;
6037
6038         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6039
6040         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6041         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6042             int count = 0;
6043
6044             while( count < adjudicateLossPlies ) {
6045                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6046
6047                 if( count & 1 ) {
6048                     score = -score; /* Flip score for winning side */
6049                 }
6050
6051                 if( score > adjudicateLossThreshold ) {
6052                     break;
6053                 }
6054
6055                 count++;
6056             }
6057
6058             if( count >= adjudicateLossPlies ) {
6059                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6060
6061                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6062                     "Xboard adjudication",
6063                     GE_XBOARD );
6064
6065                 return;
6066             }
6067         }
6068
6069         if( gameMode == TwoMachinesPlay ) {
6070           // [HGM] some adjudications useful with buggy engines
6071             int k, count = 0; static int bare = 1;
6072           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6073
6074
6075             if( appData.testLegality )
6076             {   /* [HGM] Some more adjudications for obstinate engines */
6077                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6078                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6079                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6080                 static int moveCount = 6;
6081                 ChessMove result;
6082                 char *reason = NULL;
6083
6084                 /* Count what is on board. */
6085                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6086                 {   ChessSquare p = boards[forwardMostMove][i][j];
6087                     int m=i;
6088
6089                     switch((int) p)
6090                     {   /* count B,N,R and other of each side */
6091                         case WhiteKing:
6092                         case BlackKing:
6093                              NrK++; break; // [HGM] atomic: count Kings
6094                         case WhiteKnight:
6095                              NrWN++; break;
6096                         case WhiteBishop:
6097                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6098                              bishopsColor |= 1 << ((i^j)&1);
6099                              NrWB++; break;
6100                         case BlackKnight:
6101                              NrBN++; break;
6102                         case BlackBishop:
6103                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6104                              bishopsColor |= 1 << ((i^j)&1);
6105                              NrBB++; break;
6106                         case WhiteRook:
6107                              NrWR++; break;
6108                         case BlackRook:
6109                              NrBR++; break;
6110                         case WhiteQueen:
6111                              NrWQ++; break;
6112                         case BlackQueen:
6113                              NrBQ++; break;
6114                         case EmptySquare:
6115                              break;
6116                         case BlackPawn:
6117                              m = 7-i;
6118                         case WhitePawn:
6119                              PawnAdvance += m; NrPawns++;
6120                     }
6121                     NrPieces += (p != EmptySquare);
6122                     NrW += ((int)p < (int)BlackPawn);
6123                     if(gameInfo.variant == VariantXiangqi &&
6124                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6125                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6126                         NrW -= ((int)p < (int)BlackPawn);
6127                     }
6128                 }
6129
6130                 /* Some material-based adjudications that have to be made before stalemate test */
6131                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6132                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6133                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6134                      if(appData.checkMates) {
6135                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6136                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6137                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6138                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6139                          return;
6140                      }
6141                 }
6142
6143                 /* Bare King in Shatranj (loses) or Losers (wins) */
6144                 if( NrW == 1 || NrPieces - NrW == 1) {
6145                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6146                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6147                      if(appData.checkMates) {
6148                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6149                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6150                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6151                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6152                          return;
6153                      }
6154                   } else
6155                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6156                   {    /* bare King */
6157                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6158                         if(appData.checkMates) {
6159                             /* but only adjudicate if adjudication enabled */
6160                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6161                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6162                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6163                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6164                             return;
6165                         }
6166                   }
6167                 } else bare = 1;
6168
6169
6170             // don't wait for engine to announce game end if we can judge ourselves
6171             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6172               case MT_CHECK:
6173                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6174                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6175                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6176                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6177                             checkCnt++;
6178                         if(checkCnt >= 2) {
6179                             reason = "Xboard adjudication: 3rd check";
6180                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6181                             break;
6182                         }
6183                     }
6184                 }
6185               case MT_NONE:
6186               default:
6187                 break;
6188               case MT_STALEMATE:
6189               case MT_STAINMATE:
6190                 reason = "Xboard adjudication: Stalemate";
6191                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6192                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6193                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6194                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6195                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6196                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6197                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6198                                                                         EP_CHECKMATE : EP_WINS);
6199                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6200                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6201                 }
6202                 break;
6203               case MT_CHECKMATE:
6204                 reason = "Xboard adjudication: Checkmate";
6205                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6206                 break;
6207             }
6208
6209                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6210                     case EP_STALEMATE:
6211                         result = GameIsDrawn; break;
6212                     case EP_CHECKMATE:
6213                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6214                     case EP_WINS:
6215                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6216                     default:
6217                         result = (ChessMove) 0;
6218                 }
6219                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6220                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6221                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6222                     GameEnds( result, reason, GE_XBOARD );
6223                     return;
6224                 }
6225
6226                 /* Next absolutely insufficient mating material. */
6227                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6228                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6229                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6230                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6231                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6232
6233                      /* always flag draws, for judging claims */
6234                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6235
6236                      if(appData.materialDraws) {
6237                          /* but only adjudicate them if adjudication enabled */
6238                          SendToProgram("force\n", cps->other); // suppress reply
6239                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6240                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6241                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6242                          return;
6243                      }
6244                 }
6245
6246                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6247                 if(NrPieces == 4 &&
6248                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6249                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6250                    || NrWN==2 || NrBN==2     /* KNNK */
6251                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6252                   ) ) {
6253                      if(--moveCount < 0 && appData.trivialDraws)
6254                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6255                           SendToProgram("force\n", cps->other); // suppress reply
6256                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6257                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6258                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6259                           return;
6260                      }
6261                 } else moveCount = 6;
6262             }
6263           }
6264           
6265           if (appData.debugMode) { int i;
6266             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6267                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6268                     appData.drawRepeats);
6269             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6270               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6271             
6272           }
6273
6274                 /* Check for rep-draws */
6275                 count = 0;
6276                 for(k = forwardMostMove-2;
6277                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6278                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6279                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6280                     k-=2)
6281                 {   int rights=0;
6282                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6283                         /* compare castling rights */
6284                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6285                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6286                                 rights++; /* King lost rights, while rook still had them */
6287                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6288                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6289                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6290                                    rights++; /* but at least one rook lost them */
6291                         }
6292                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6293                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6294                                 rights++; 
6295                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6296                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6297                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6298                                    rights++;
6299                         }
6300                         if( rights == 0 && ++count > appData.drawRepeats-2
6301                             && appData.drawRepeats > 1) {
6302                              /* adjudicate after user-specified nr of repeats */
6303                              SendToProgram("force\n", cps->other); // suppress reply
6304                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6305                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6306                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6307                                 // [HGM] xiangqi: check for forbidden perpetuals
6308                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6309                                 for(m=forwardMostMove; m>k; m-=2) {
6310                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6311                                         ourPerpetual = 0; // the current mover did not always check
6312                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6313                                         hisPerpetual = 0; // the opponent did not always check
6314                                 }
6315                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6316                                                                         ourPerpetual, hisPerpetual);
6317                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6318                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6319                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6320                                     return;
6321                                 }
6322                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6323                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6324                                 // Now check for perpetual chases
6325                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6326                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6327                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6328                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6329                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6330                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6331                                         return;
6332                                     }
6333                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6334                                         break; // Abort repetition-checking loop.
6335                                 }
6336                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6337                              }
6338                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6339                              return;
6340                         }
6341                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6342                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6343                     }
6344                 }
6345
6346                 /* Now we test for 50-move draws. Determine ply count */
6347                 count = forwardMostMove;
6348                 /* look for last irreversble move */
6349                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6350                     count--;
6351                 /* if we hit starting position, add initial plies */
6352                 if( count == backwardMostMove )
6353                     count -= initialRulePlies;
6354                 count = forwardMostMove - count;
6355                 if( count >= 100)
6356                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6357                          /* this is used to judge if draw claims are legal */
6358                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6359                          SendToProgram("force\n", cps->other); // suppress reply
6360                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6361                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6362                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6363                          return;
6364                 }
6365
6366                 /* if draw offer is pending, treat it as a draw claim
6367                  * when draw condition present, to allow engines a way to
6368                  * claim draws before making their move to avoid a race
6369                  * condition occurring after their move
6370                  */
6371                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6372                          char *p = NULL;
6373                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6374                              p = "Draw claim: 50-move rule";
6375                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6376                              p = "Draw claim: 3-fold repetition";
6377                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6378                              p = "Draw claim: insufficient mating material";
6379                          if( p != NULL ) {
6380                              SendToProgram("force\n", cps->other); // suppress reply
6381                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6382                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6383                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6384                              return;
6385                          }
6386                 }
6387
6388
6389                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6390                     SendToProgram("force\n", cps->other); // suppress reply
6391                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6392                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6393
6394                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6395
6396                     return;
6397                 }
6398         }
6399
6400         bookHit = NULL;
6401         if (gameMode == TwoMachinesPlay) {
6402             /* [HGM] relaying draw offers moved to after reception of move */
6403             /* and interpreting offer as claim if it brings draw condition */
6404             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6405                 SendToProgram("draw\n", cps->other);
6406             }
6407             if (cps->other->sendTime) {
6408                 SendTimeRemaining(cps->other,
6409                                   cps->other->twoMachinesColor[0] == 'w');
6410             }
6411             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6412             if (firstMove && !bookHit) {
6413                 firstMove = FALSE;
6414                 if (cps->other->useColors) {
6415                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6416                 }
6417                 SendToProgram("go\n", cps->other);
6418             }
6419             cps->other->maybeThinking = TRUE;
6420         }
6421
6422         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6423
6424         if (!pausing && appData.ringBellAfterMoves) {
6425             RingBell();
6426         }
6427
6428         /*
6429          * Reenable menu items that were disabled while
6430          * machine was thinking
6431          */
6432         if (gameMode != TwoMachinesPlay)
6433             SetUserThinkingEnables();
6434
6435         // [HGM] book: after book hit opponent has received move and is now in force mode
6436         // force the book reply into it, and then fake that it outputted this move by jumping
6437         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6438         if(bookHit) {
6439                 static char bookMove[MSG_SIZ]; // a bit generous?
6440
6441                 strcpy(bookMove, "move ");
6442                 strcat(bookMove, bookHit);
6443                 message = bookMove;
6444                 cps = cps->other;
6445                 programStats.nodes = programStats.depth = programStats.time =
6446                 programStats.score = programStats.got_only_move = 0;
6447                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6448
6449                 if(cps->lastPing != cps->lastPong) {
6450                     savedMessage = message; // args for deferred call
6451                     savedState = cps;
6452                     ScheduleDelayedEvent(DeferredBookMove, 10);
6453                     return;
6454                 }
6455                 goto FakeBookMove;
6456         }
6457
6458         return;
6459     }
6460
6461     /* Set special modes for chess engines.  Later something general
6462      *  could be added here; for now there is just one kludge feature,
6463      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6464      *  when "xboard" is given as an interactive command.
6465      */
6466     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6467         cps->useSigint = FALSE;
6468         cps->useSigterm = FALSE;
6469     }
6470     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6471       ParseFeatures(message+8, cps);
6472       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6473     }
6474
6475     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6476      * want this, I was asked to put it in, and obliged.
6477      */
6478     if (!strncmp(message, "setboard ", 9)) {
6479         Board initial_position;
6480
6481         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6482
6483         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6484             DisplayError(_("Bad FEN received from engine"), 0);
6485             return ;
6486         } else {
6487            Reset(TRUE, FALSE);
6488            CopyBoard(boards[0], initial_position);
6489            initialRulePlies = FENrulePlies;
6490            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6491            else gameMode = MachinePlaysBlack;
6492            DrawPosition(FALSE, boards[currentMove]);
6493         }
6494         return;
6495     }
6496
6497     /*
6498      * Look for communication commands
6499      */
6500     if (!strncmp(message, "telluser ", 9)) {
6501         DisplayNote(message + 9);
6502         return;
6503     }
6504     if (!strncmp(message, "tellusererror ", 14)) {
6505         DisplayError(message + 14, 0);
6506         return;
6507     }
6508     if (!strncmp(message, "tellopponent ", 13)) {
6509       if (appData.icsActive) {
6510         if (loggedOn) {
6511           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6512           SendToICS(buf1);
6513         }
6514       } else {
6515         DisplayNote(message + 13);
6516       }
6517       return;
6518     }
6519     if (!strncmp(message, "tellothers ", 11)) {
6520       if (appData.icsActive) {
6521         if (loggedOn) {
6522           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6523           SendToICS(buf1);
6524         }
6525       }
6526       return;
6527     }
6528     if (!strncmp(message, "tellall ", 8)) {
6529       if (appData.icsActive) {
6530         if (loggedOn) {
6531           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6532           SendToICS(buf1);
6533         }
6534       } else {
6535         DisplayNote(message + 8);
6536       }
6537       return;
6538     }
6539     if (strncmp(message, "warning", 7) == 0) {
6540         /* Undocumented feature, use tellusererror in new code */
6541         DisplayError(message, 0);
6542         return;
6543     }
6544     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6545         strcpy(realname, cps->tidy);
6546         strcat(realname, " query");
6547         AskQuestion(realname, buf2, buf1, cps->pr);
6548         return;
6549     }
6550     /* Commands from the engine directly to ICS.  We don't allow these to be
6551      *  sent until we are logged on. Crafty kibitzes have been known to
6552      *  interfere with the login process.
6553      */
6554     if (loggedOn) {
6555         if (!strncmp(message, "tellics ", 8)) {
6556             SendToICS(message + 8);
6557             SendToICS("\n");
6558             return;
6559         }
6560         if (!strncmp(message, "tellicsnoalias ", 15)) {
6561             SendToICS(ics_prefix);
6562             SendToICS(message + 15);
6563             SendToICS("\n");
6564             return;
6565         }
6566         /* The following are for backward compatibility only */
6567         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6568             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6569             SendToICS(ics_prefix);
6570             SendToICS(message);
6571             SendToICS("\n");
6572             return;
6573         }
6574     }
6575     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6576         return;
6577     }
6578     /*
6579      * If the move is illegal, cancel it and redraw the board.
6580      * Also deal with other error cases.  Matching is rather loose
6581      * here to accommodate engines written before the spec.
6582      */
6583     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6584         strncmp(message, "Error", 5) == 0) {
6585         if (StrStr(message, "name") ||
6586             StrStr(message, "rating") || StrStr(message, "?") ||
6587             StrStr(message, "result") || StrStr(message, "board") ||
6588             StrStr(message, "bk") || StrStr(message, "computer") ||
6589             StrStr(message, "variant") || StrStr(message, "hint") ||
6590             StrStr(message, "random") || StrStr(message, "depth") ||
6591             StrStr(message, "accepted")) {
6592             return;
6593         }
6594         if (StrStr(message, "protover")) {
6595           /* Program is responding to input, so it's apparently done
6596              initializing, and this error message indicates it is
6597              protocol version 1.  So we don't need to wait any longer
6598              for it to initialize and send feature commands. */
6599           FeatureDone(cps, 1);
6600           cps->protocolVersion = 1;
6601           return;
6602         }
6603         cps->maybeThinking = FALSE;
6604
6605         if (StrStr(message, "draw")) {
6606             /* Program doesn't have "draw" command */
6607             cps->sendDrawOffers = 0;
6608             return;
6609         }
6610         if (cps->sendTime != 1 &&
6611             (StrStr(message, "time") || StrStr(message, "otim"))) {
6612           /* Program apparently doesn't have "time" or "otim" command */
6613           cps->sendTime = 0;
6614           return;
6615         }
6616         if (StrStr(message, "analyze")) {
6617             cps->analysisSupport = FALSE;
6618             cps->analyzing = FALSE;
6619             Reset(FALSE, TRUE);
6620             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6621             DisplayError(buf2, 0);
6622             return;
6623         }
6624         if (StrStr(message, "(no matching move)st")) {
6625           /* Special kludge for GNU Chess 4 only */
6626           cps->stKludge = TRUE;
6627           SendTimeControl(cps, movesPerSession, timeControl,
6628                           timeIncrement, appData.searchDepth,
6629                           searchTime);
6630           return;
6631         }
6632         if (StrStr(message, "(no matching move)sd")) {
6633           /* Special kludge for GNU Chess 4 only */
6634           cps->sdKludge = TRUE;
6635           SendTimeControl(cps, movesPerSession, timeControl,
6636                           timeIncrement, appData.searchDepth,
6637                           searchTime);
6638           return;
6639         }
6640         if (!StrStr(message, "llegal")) {
6641             return;
6642         }
6643         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6644             gameMode == IcsIdle) return;
6645         if (forwardMostMove <= backwardMostMove) return;
6646         if (pausing) PauseEvent();
6647       if(appData.forceIllegal) {
6648             // [HGM] illegal: machine refused move; force position after move into it
6649           SendToProgram("force\n", cps);
6650           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6651                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6652                 // when black is to move, while there might be nothing on a2 or black
6653                 // might already have the move. So send the board as if white has the move.
6654                 // But first we must change the stm of the engine, as it refused the last move
6655                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6656                 if(WhiteOnMove(forwardMostMove)) {
6657                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6658                     SendBoard(cps, forwardMostMove); // kludgeless board
6659                 } else {
6660                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6661                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6662                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6663                 }
6664           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6665             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6666                  gameMode == TwoMachinesPlay)
6667               SendToProgram("go\n", cps);
6668             return;
6669       } else
6670         if (gameMode == PlayFromGameFile) {
6671             /* Stop reading this game file */
6672             gameMode = EditGame;
6673             ModeHighlight();
6674         }
6675         currentMove = --forwardMostMove;
6676         DisplayMove(currentMove-1); /* before DisplayMoveError */
6677         SwitchClocks();
6678         DisplayBothClocks();
6679         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6680                 parseList[currentMove], cps->which);
6681         DisplayMoveError(buf1);
6682         DrawPosition(FALSE, boards[currentMove]);
6683
6684         /* [HGM] illegal-move claim should forfeit game when Xboard */
6685         /* only passes fully legal moves                            */
6686         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6687             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6688                                 "False illegal-move claim", GE_XBOARD );
6689         }
6690         return;
6691     }
6692     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6693         /* Program has a broken "time" command that
6694            outputs a string not ending in newline.
6695            Don't use it. */
6696         cps->sendTime = 0;
6697     }
6698
6699     /*
6700      * If chess program startup fails, exit with an error message.
6701      * Attempts to recover here are futile.
6702      */
6703     if ((StrStr(message, "unknown host") != NULL)
6704         || (StrStr(message, "No remote directory") != NULL)
6705         || (StrStr(message, "not found") != NULL)
6706         || (StrStr(message, "No such file") != NULL)
6707         || (StrStr(message, "can't alloc") != NULL)
6708         || (StrStr(message, "Permission denied") != NULL)) {
6709
6710         cps->maybeThinking = FALSE;
6711         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6712                 cps->which, cps->program, cps->host, message);
6713         RemoveInputSource(cps->isr);
6714         DisplayFatalError(buf1, 0, 1);
6715         return;
6716     }
6717
6718     /*
6719      * Look for hint output
6720      */
6721     if (sscanf(message, "Hint: %s", buf1) == 1) {
6722         if (cps == &first && hintRequested) {
6723             hintRequested = FALSE;
6724             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6725                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6726                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6727                                     PosFlags(forwardMostMove),
6728                                     fromY, fromX, toY, toX, promoChar, buf1);
6729                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6730                 DisplayInformation(buf2);
6731             } else {
6732                 /* Hint move could not be parsed!? */
6733               snprintf(buf2, sizeof(buf2),
6734                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6735                         buf1, cps->which);
6736                 DisplayError(buf2, 0);
6737             }
6738         } else {
6739             strcpy(lastHint, buf1);
6740         }
6741         return;
6742     }
6743
6744     /*
6745      * Ignore other messages if game is not in progress
6746      */
6747     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6748         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6749
6750     /*
6751      * look for win, lose, draw, or draw offer
6752      */
6753     if (strncmp(message, "1-0", 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         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6764         return;
6765     } else if (strncmp(message, "0-1", 3) == 0) {
6766         char *p, *q, *r = "";
6767         p = strchr(message, '{');
6768         if (p) {
6769             q = strchr(p, '}');
6770             if (q) {
6771                 *q = NULLCHAR;
6772                 r = p + 1;
6773             }
6774         }
6775         /* Kludge for Arasan 4.1 bug */
6776         if (strcmp(r, "Black resigns") == 0) {
6777             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6778             return;
6779         }
6780         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6781         return;
6782     } else if (strncmp(message, "1/2", 3) == 0) {
6783         char *p, *q, *r = "";
6784         p = strchr(message, '{');
6785         if (p) {
6786             q = strchr(p, '}');
6787             if (q) {
6788                 *q = NULLCHAR;
6789                 r = p + 1;
6790             }
6791         }
6792
6793         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6794         return;
6795
6796     } else if (strncmp(message, "White resign", 12) == 0) {
6797         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6798         return;
6799     } else if (strncmp(message, "Black resign", 12) == 0) {
6800         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6801         return;
6802     } else if (strncmp(message, "White matches", 13) == 0 ||
6803                strncmp(message, "Black matches", 13) == 0   ) {
6804         /* [HGM] ignore GNUShogi noises */
6805         return;
6806     } else if (strncmp(message, "White", 5) == 0 &&
6807                message[5] != '(' &&
6808                StrStr(message, "Black") == NULL) {
6809         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6810         return;
6811     } else if (strncmp(message, "Black", 5) == 0 &&
6812                message[5] != '(') {
6813         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6814         return;
6815     } else if (strcmp(message, "resign") == 0 ||
6816                strcmp(message, "computer resigns") == 0) {
6817         switch (gameMode) {
6818           case MachinePlaysBlack:
6819           case IcsPlayingBlack:
6820             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6821             break;
6822           case MachinePlaysWhite:
6823           case IcsPlayingWhite:
6824             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6825             break;
6826           case TwoMachinesPlay:
6827             if (cps->twoMachinesColor[0] == 'w')
6828               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6829             else
6830               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6831             break;
6832           default:
6833             /* can't happen */
6834             break;
6835         }
6836         return;
6837     } else if (strncmp(message, "opponent mates", 14) == 0) {
6838         switch (gameMode) {
6839           case MachinePlaysBlack:
6840           case IcsPlayingBlack:
6841             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6842             break;
6843           case MachinePlaysWhite:
6844           case IcsPlayingWhite:
6845             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6846             break;
6847           case TwoMachinesPlay:
6848             if (cps->twoMachinesColor[0] == 'w')
6849               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6850             else
6851               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6852             break;
6853           default:
6854             /* can't happen */
6855             break;
6856         }
6857         return;
6858     } else if (strncmp(message, "computer mates", 14) == 0) {
6859         switch (gameMode) {
6860           case MachinePlaysBlack:
6861           case IcsPlayingBlack:
6862             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6863             break;
6864           case MachinePlaysWhite:
6865           case IcsPlayingWhite:
6866             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6867             break;
6868           case TwoMachinesPlay:
6869             if (cps->twoMachinesColor[0] == 'w')
6870               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6871             else
6872               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6873             break;
6874           default:
6875             /* can't happen */
6876             break;
6877         }
6878         return;
6879     } else if (strncmp(message, "checkmate", 9) == 0) {
6880         if (WhiteOnMove(forwardMostMove)) {
6881             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6882         } else {
6883             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6884         }
6885         return;
6886     } else if (strstr(message, "Draw") != NULL ||
6887                strstr(message, "game is a draw") != NULL) {
6888         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6889         return;
6890     } else if (strstr(message, "offer") != NULL &&
6891                strstr(message, "draw") != NULL) {
6892 #if ZIPPY
6893         if (appData.zippyPlay && first.initDone) {
6894             /* Relay offer to ICS */
6895             SendToICS(ics_prefix);
6896             SendToICS("draw\n");
6897         }
6898 #endif
6899         cps->offeredDraw = 2; /* valid until this engine moves twice */
6900         if (gameMode == TwoMachinesPlay) {
6901             if (cps->other->offeredDraw) {
6902                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6903             /* [HGM] in two-machine mode we delay relaying draw offer      */
6904             /* until after we also have move, to see if it is really claim */
6905             }
6906         } else if (gameMode == MachinePlaysWhite ||
6907                    gameMode == MachinePlaysBlack) {
6908           if (userOfferedDraw) {
6909             DisplayInformation(_("Machine accepts your draw offer"));
6910             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6911           } else {
6912             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6913           }
6914         }
6915     }
6916
6917
6918     /*
6919      * Look for thinking output
6920      */
6921     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6922           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6923                                 ) {
6924         int plylev, mvleft, mvtot, curscore, time;
6925         char mvname[MOVE_LEN];
6926         u64 nodes; // [DM]
6927         char plyext;
6928         int ignore = FALSE;
6929         int prefixHint = FALSE;
6930         mvname[0] = NULLCHAR;
6931
6932         switch (gameMode) {
6933           case MachinePlaysBlack:
6934           case IcsPlayingBlack:
6935             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6936             break;
6937           case MachinePlaysWhite:
6938           case IcsPlayingWhite:
6939             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6940             break;
6941           case AnalyzeMode:
6942           case AnalyzeFile:
6943             break;
6944           case IcsObserving: /* [DM] icsEngineAnalyze */
6945             if (!appData.icsEngineAnalyze) ignore = TRUE;
6946             break;
6947           case TwoMachinesPlay:
6948             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6949                 ignore = TRUE;
6950             }
6951             break;
6952           default:
6953             ignore = TRUE;
6954             break;
6955         }
6956
6957         if (!ignore) {
6958             buf1[0] = NULLCHAR;
6959             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6960                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6961
6962                 if (plyext != ' ' && plyext != '\t') {
6963                     time *= 100;
6964                 }
6965
6966                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6967                 if( cps->scoreIsAbsolute &&
6968                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6969                 {
6970                     curscore = -curscore;
6971                 }
6972
6973
6974                 programStats.depth = plylev;
6975                 programStats.nodes = nodes;
6976                 programStats.time = time;
6977                 programStats.score = curscore;
6978                 programStats.got_only_move = 0;
6979
6980                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6981                         int ticklen;
6982
6983                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6984                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6985                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6986                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6987                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6988                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6989                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6990                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6991                 }
6992
6993                 /* Buffer overflow protection */
6994                 if (buf1[0] != NULLCHAR) {
6995                     if (strlen(buf1) >= sizeof(programStats.movelist)
6996                         && appData.debugMode) {
6997                         fprintf(debugFP,
6998                                 "PV is too long; using the first %u bytes.\n",
6999                                 (unsigned) sizeof(programStats.movelist) - 1);
7000                     }
7001
7002                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7003                 } else {
7004                     sprintf(programStats.movelist, " no PV\n");
7005                 }
7006
7007                 if (programStats.seen_stat) {
7008                     programStats.ok_to_send = 1;
7009                 }
7010
7011                 if (strchr(programStats.movelist, '(') != NULL) {
7012                     programStats.line_is_book = 1;
7013                     programStats.nr_moves = 0;
7014                     programStats.moves_left = 0;
7015                 } else {
7016                     programStats.line_is_book = 0;
7017                 }
7018
7019                 SendProgramStatsToFrontend( cps, &programStats );
7020
7021                 /*
7022                     [AS] Protect the thinkOutput buffer from overflow... this
7023                     is only useful if buf1 hasn't overflowed first!
7024                 */
7025                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7026                         plylev,
7027                         (gameMode == TwoMachinesPlay ?
7028                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7029                         ((double) curscore) / 100.0,
7030                         prefixHint ? lastHint : "",
7031                         prefixHint ? " " : "" );
7032
7033                 if( buf1[0] != NULLCHAR ) {
7034                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7035
7036                     if( strlen(buf1) > max_len ) {
7037                         if( appData.debugMode) {
7038                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7039                         }
7040                         buf1[max_len+1] = '\0';
7041                     }
7042
7043                     strcat( thinkOutput, buf1 );
7044                 }
7045
7046                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7047                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7048                     DisplayMove(currentMove - 1);
7049                 }
7050                 return;
7051
7052             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7053                 /* crafty (9.25+) says "(only move) <move>"
7054                  * if there is only 1 legal move
7055                  */
7056                 sscanf(p, "(only move) %s", buf1);
7057                 sprintf(thinkOutput, "%s (only move)", buf1);
7058                 sprintf(programStats.movelist, "%s (only move)", buf1);
7059                 programStats.depth = 1;
7060                 programStats.nr_moves = 1;
7061                 programStats.moves_left = 1;
7062                 programStats.nodes = 1;
7063                 programStats.time = 1;
7064                 programStats.got_only_move = 1;
7065
7066                 /* Not really, but we also use this member to
7067                    mean "line isn't going to change" (Crafty
7068                    isn't searching, so stats won't change) */
7069                 programStats.line_is_book = 1;
7070
7071                 SendProgramStatsToFrontend( cps, &programStats );
7072
7073                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7074                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7075                     DisplayMove(currentMove - 1);
7076                 }
7077                 return;
7078             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7079                               &time, &nodes, &plylev, &mvleft,
7080                               &mvtot, mvname) >= 5) {
7081                 /* The stat01: line is from Crafty (9.29+) in response
7082                    to the "." command */
7083                 programStats.seen_stat = 1;
7084                 cps->maybeThinking = TRUE;
7085
7086                 if (programStats.got_only_move || !appData.periodicUpdates)
7087                   return;
7088
7089                 programStats.depth = plylev;
7090                 programStats.time = time;
7091                 programStats.nodes = nodes;
7092                 programStats.moves_left = mvleft;
7093                 programStats.nr_moves = mvtot;
7094                 strcpy(programStats.move_name, mvname);
7095                 programStats.ok_to_send = 1;
7096                 programStats.movelist[0] = '\0';
7097
7098                 SendProgramStatsToFrontend( cps, &programStats );
7099
7100                 return;
7101
7102             } else if (strncmp(message,"++",2) == 0) {
7103                 /* Crafty 9.29+ outputs this */
7104                 programStats.got_fail = 2;
7105                 return;
7106
7107             } else if (strncmp(message,"--",2) == 0) {
7108                 /* Crafty 9.29+ outputs this */
7109                 programStats.got_fail = 1;
7110                 return;
7111
7112             } else if (thinkOutput[0] != NULLCHAR &&
7113                        strncmp(message, "    ", 4) == 0) {
7114                 unsigned message_len;
7115
7116                 p = message;
7117                 while (*p && *p == ' ') p++;
7118
7119                 message_len = strlen( p );
7120
7121                 /* [AS] Avoid buffer overflow */
7122                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7123                     strcat(thinkOutput, " ");
7124                     strcat(thinkOutput, p);
7125                 }
7126
7127                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7128                     strcat(programStats.movelist, " ");
7129                     strcat(programStats.movelist, p);
7130                 }
7131
7132                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7133                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7134                     DisplayMove(currentMove - 1);
7135                 }
7136                 return;
7137             }
7138         }
7139         else {
7140             buf1[0] = NULLCHAR;
7141
7142             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7143                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7144             {
7145                 ChessProgramStats cpstats;
7146
7147                 if (plyext != ' ' && plyext != '\t') {
7148                     time *= 100;
7149                 }
7150
7151                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7152                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7153                     curscore = -curscore;
7154                 }
7155
7156                 cpstats.depth = plylev;
7157                 cpstats.nodes = nodes;
7158                 cpstats.time = time;
7159                 cpstats.score = curscore;
7160                 cpstats.got_only_move = 0;
7161                 cpstats.movelist[0] = '\0';
7162
7163                 if (buf1[0] != NULLCHAR) {
7164                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7165                 }
7166
7167                 cpstats.ok_to_send = 0;
7168                 cpstats.line_is_book = 0;
7169                 cpstats.nr_moves = 0;
7170                 cpstats.moves_left = 0;
7171
7172                 SendProgramStatsToFrontend( cps, &cpstats );
7173             }
7174         }
7175     }
7176 }
7177
7178
7179 /* Parse a game score from the character string "game", and
7180    record it as the history of the current game.  The game
7181    score is NOT assumed to start from the standard position.
7182    The display is not updated in any way.
7183    */
7184 void
7185 ParseGameHistory(game)
7186      char *game;
7187 {
7188     ChessMove moveType;
7189     int fromX, fromY, toX, toY, boardIndex;
7190     char promoChar;
7191     char *p, *q;
7192     char buf[MSG_SIZ];
7193
7194     if (appData.debugMode)
7195       fprintf(debugFP, "Parsing game history: %s\n", game);
7196
7197     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7198     gameInfo.site = StrSave(appData.icsHost);
7199     gameInfo.date = PGNDate();
7200     gameInfo.round = StrSave("-");
7201
7202     /* Parse out names of players */
7203     while (*game == ' ') game++;
7204     p = buf;
7205     while (*game != ' ') *p++ = *game++;
7206     *p = NULLCHAR;
7207     gameInfo.white = StrSave(buf);
7208     while (*game == ' ') game++;
7209     p = buf;
7210     while (*game != ' ' && *game != '\n') *p++ = *game++;
7211     *p = NULLCHAR;
7212     gameInfo.black = StrSave(buf);
7213
7214     /* Parse moves */
7215     boardIndex = blackPlaysFirst ? 1 : 0;
7216     yynewstr(game);
7217     for (;;) {
7218         yyboardindex = boardIndex;
7219         moveType = (ChessMove) yylex();
7220         switch (moveType) {
7221           case IllegalMove:             /* maybe suicide chess, etc. */
7222   if (appData.debugMode) {
7223     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7224     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7225     setbuf(debugFP, NULL);
7226   }
7227           case WhitePromotionChancellor:
7228           case BlackPromotionChancellor:
7229           case WhitePromotionArchbishop:
7230           case BlackPromotionArchbishop:
7231           case WhitePromotionQueen:
7232           case BlackPromotionQueen:
7233           case WhitePromotionRook:
7234           case BlackPromotionRook:
7235           case WhitePromotionBishop:
7236           case BlackPromotionBishop:
7237           case WhitePromotionKnight:
7238           case BlackPromotionKnight:
7239           case WhitePromotionKing:
7240           case BlackPromotionKing:
7241           case NormalMove:
7242           case WhiteCapturesEnPassant:
7243           case BlackCapturesEnPassant:
7244           case WhiteKingSideCastle:
7245           case WhiteQueenSideCastle:
7246           case BlackKingSideCastle:
7247           case BlackQueenSideCastle:
7248           case WhiteKingSideCastleWild:
7249           case WhiteQueenSideCastleWild:
7250           case BlackKingSideCastleWild:
7251           case BlackQueenSideCastleWild:
7252           /* PUSH Fabien */
7253           case WhiteHSideCastleFR:
7254           case WhiteASideCastleFR:
7255           case BlackHSideCastleFR:
7256           case BlackASideCastleFR:
7257           /* POP Fabien */
7258             fromX = currentMoveString[0] - AAA;
7259             fromY = currentMoveString[1] - ONE;
7260             toX = currentMoveString[2] - AAA;
7261             toY = currentMoveString[3] - ONE;
7262             promoChar = currentMoveString[4];
7263             break;
7264           case WhiteDrop:
7265           case BlackDrop:
7266             fromX = moveType == WhiteDrop ?
7267               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7268             (int) CharToPiece(ToLower(currentMoveString[0]));
7269             fromY = DROP_RANK;
7270             toX = currentMoveString[2] - AAA;
7271             toY = currentMoveString[3] - ONE;
7272             promoChar = NULLCHAR;
7273             break;
7274           case AmbiguousMove:
7275             /* bug? */
7276             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7277   if (appData.debugMode) {
7278     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7279     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7280     setbuf(debugFP, NULL);
7281   }
7282             DisplayError(buf, 0);
7283             return;
7284           case ImpossibleMove:
7285             /* bug? */
7286             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7287   if (appData.debugMode) {
7288     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7289     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7290     setbuf(debugFP, NULL);
7291   }
7292             DisplayError(buf, 0);
7293             return;
7294           case (ChessMove) 0:   /* end of file */
7295             if (boardIndex < backwardMostMove) {
7296                 /* Oops, gap.  How did that happen? */
7297                 DisplayError(_("Gap in move list"), 0);
7298                 return;
7299             }
7300             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7301             if (boardIndex > forwardMostMove) {
7302                 forwardMostMove = boardIndex;
7303             }
7304             return;
7305           case ElapsedTime:
7306             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7307                 strcat(parseList[boardIndex-1], " ");
7308                 strcat(parseList[boardIndex-1], yy_text);
7309             }
7310             continue;
7311           case Comment:
7312           case PGNTag:
7313           case NAG:
7314           default:
7315             /* ignore */
7316             continue;
7317           case WhiteWins:
7318           case BlackWins:
7319           case GameIsDrawn:
7320           case GameUnfinished:
7321             if (gameMode == IcsExamining) {
7322                 if (boardIndex < backwardMostMove) {
7323                     /* Oops, gap.  How did that happen? */
7324                     return;
7325                 }
7326                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7327                 return;
7328             }
7329             gameInfo.result = moveType;
7330             p = strchr(yy_text, '{');
7331             if (p == NULL) p = strchr(yy_text, '(');
7332             if (p == NULL) {
7333                 p = yy_text;
7334                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7335             } else {
7336                 q = strchr(p, *p == '{' ? '}' : ')');
7337                 if (q != NULL) *q = NULLCHAR;
7338                 p++;
7339             }
7340             gameInfo.resultDetails = StrSave(p);
7341             continue;
7342         }
7343         if (boardIndex >= forwardMostMove &&
7344             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7345             backwardMostMove = blackPlaysFirst ? 1 : 0;
7346             return;
7347         }
7348         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7349                                  fromY, fromX, toY, toX, promoChar,
7350                                  parseList[boardIndex]);
7351         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7352         /* currentMoveString is set as a side-effect of yylex */
7353         strcpy(moveList[boardIndex], currentMoveString);
7354         strcat(moveList[boardIndex], "\n");
7355         boardIndex++;
7356         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7357         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7358           case MT_NONE:
7359           case MT_STALEMATE:
7360           default:
7361             break;
7362           case MT_CHECK:
7363             if(gameInfo.variant != VariantShogi)
7364                 strcat(parseList[boardIndex - 1], "+");
7365             break;
7366           case MT_CHECKMATE:
7367           case MT_STAINMATE:
7368             strcat(parseList[boardIndex - 1], "#");
7369             break;
7370         }
7371     }
7372 }
7373
7374
7375 /* Apply a move to the given board  */
7376 void
7377 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7378      int fromX, fromY, toX, toY;
7379      int promoChar;
7380      Board board;
7381 {
7382   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7383
7384     /* [HGM] compute & store e.p. status and castling rights for new position */
7385     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7386     { int i;
7387
7388       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7389       oldEP = (signed char)board[EP_STATUS];
7390       board[EP_STATUS] = EP_NONE;
7391
7392       if( board[toY][toX] != EmptySquare ) 
7393            board[EP_STATUS] = EP_CAPTURE;  
7394
7395       if( board[fromY][fromX] == WhitePawn ) {
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] == BlackPawn &&
7400                         gameInfo.variant != VariantBerolina || toX < fromX)
7401                       board[EP_STATUS] = toX | berolina;
7402                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7403                   gameInfo.variant != VariantBerolina || toX > fromX) 
7404                  board[EP_STATUS] = toX;
7405            }
7406       } else
7407       if( board[fromY][fromX] == BlackPawn ) {
7408            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7409                board[EP_STATUS] = EP_PAWN_MOVE; 
7410            if( toY-fromY== -2) {
7411                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7412                         gameInfo.variant != VariantBerolina || toX < fromX)
7413                       board[EP_STATUS] = toX | berolina;
7414                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7415                         gameInfo.variant != VariantBerolina || toX > fromX) 
7416                       board[EP_STATUS] = toX;
7417            }
7418        }
7419
7420        for(i=0; i<nrCastlingRights; i++) {
7421            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7422               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7423              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7424        }
7425
7426     }
7427
7428   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7429   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7430        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7431
7432   if (fromX == toX && fromY == toY) return;
7433
7434   if (fromY == DROP_RANK) {
7435         /* must be first */
7436         piece = board[toY][toX] = (ChessSquare) fromX;
7437   } else {
7438      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7439      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7440      if(gameInfo.variant == VariantKnightmate)
7441          king += (int) WhiteUnicorn - (int) WhiteKing;
7442
7443     /* Code added by Tord: */
7444     /* FRC castling assumed when king captures friendly rook. */
7445     if (board[fromY][fromX] == WhiteKing &&
7446              board[toY][toX] == WhiteRook) {
7447       board[fromY][fromX] = EmptySquare;
7448       board[toY][toX] = EmptySquare;
7449       if(toX > fromX) {
7450         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7451       } else {
7452         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7453       }
7454     } else if (board[fromY][fromX] == BlackKing &&
7455                board[toY][toX] == BlackRook) {
7456       board[fromY][fromX] = EmptySquare;
7457       board[toY][toX] = EmptySquare;
7458       if(toX > fromX) {
7459         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7460       } else {
7461         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7462       }
7463     /* End of code added by Tord */
7464
7465     } else if (board[fromY][fromX] == king
7466         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7467         && toY == fromY && toX > fromX+1) {
7468         board[fromY][fromX] = EmptySquare;
7469         board[toY][toX] = king;
7470         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7471         board[fromY][BOARD_RGHT-1] = EmptySquare;
7472     } else if (board[fromY][fromX] == king
7473         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7474                && toY == fromY && toX < fromX-1) {
7475         board[fromY][fromX] = EmptySquare;
7476         board[toY][toX] = king;
7477         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7478         board[fromY][BOARD_LEFT] = EmptySquare;
7479     } else if (board[fromY][fromX] == WhitePawn
7480                && toY == BOARD_HEIGHT-1
7481                && gameInfo.variant != VariantXiangqi
7482                ) {
7483         /* white pawn promotion */
7484         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7485         if (board[toY][toX] == EmptySquare) {
7486             board[toY][toX] = WhiteQueen;
7487         }
7488         if(gameInfo.variant==VariantBughouse ||
7489            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7490             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7491         board[fromY][fromX] = EmptySquare;
7492     } else if ((fromY == BOARD_HEIGHT-4)
7493                && (toX != fromX)
7494                && gameInfo.variant != VariantXiangqi
7495                && gameInfo.variant != VariantBerolina
7496                && (board[fromY][fromX] == WhitePawn)
7497                && (board[toY][toX] == EmptySquare)) {
7498         board[fromY][fromX] = EmptySquare;
7499         board[toY][toX] = WhitePawn;
7500         captured = board[toY - 1][toX];
7501         board[toY - 1][toX] = EmptySquare;
7502     } else if ((fromY == BOARD_HEIGHT-4)
7503                && (toX == fromX)
7504                && gameInfo.variant == VariantBerolina
7505                && (board[fromY][fromX] == WhitePawn)
7506                && (board[toY][toX] == EmptySquare)) {
7507         board[fromY][fromX] = EmptySquare;
7508         board[toY][toX] = WhitePawn;
7509         if(oldEP & EP_BEROLIN_A) {
7510                 captured = board[fromY][fromX-1];
7511                 board[fromY][fromX-1] = EmptySquare;
7512         }else{  captured = board[fromY][fromX+1];
7513                 board[fromY][fromX+1] = EmptySquare;
7514         }
7515     } else if (board[fromY][fromX] == king
7516         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7517                && toY == fromY && toX > fromX+1) {
7518         board[fromY][fromX] = EmptySquare;
7519         board[toY][toX] = king;
7520         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7521         board[fromY][BOARD_RGHT-1] = EmptySquare;
7522     } else if (board[fromY][fromX] == king
7523         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7524                && toY == fromY && toX < fromX-1) {
7525         board[fromY][fromX] = EmptySquare;
7526         board[toY][toX] = king;
7527         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7528         board[fromY][BOARD_LEFT] = EmptySquare;
7529     } else if (fromY == 7 && fromX == 3
7530                && board[fromY][fromX] == BlackKing
7531                && toY == 7 && toX == 5) {
7532         board[fromY][fromX] = EmptySquare;
7533         board[toY][toX] = BlackKing;
7534         board[fromY][7] = EmptySquare;
7535         board[toY][4] = BlackRook;
7536     } else if (fromY == 7 && fromX == 3
7537                && board[fromY][fromX] == BlackKing
7538                && toY == 7 && toX == 1) {
7539         board[fromY][fromX] = EmptySquare;
7540         board[toY][toX] = BlackKing;
7541         board[fromY][0] = EmptySquare;
7542         board[toY][2] = BlackRook;
7543     } else if (board[fromY][fromX] == BlackPawn
7544                && toY == 0
7545                && gameInfo.variant != VariantXiangqi
7546                ) {
7547         /* black pawn promotion */
7548         board[0][toX] = CharToPiece(ToLower(promoChar));
7549         if (board[0][toX] == EmptySquare) {
7550             board[0][toX] = BlackQueen;
7551         }
7552         if(gameInfo.variant==VariantBughouse ||
7553            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7554             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7555         board[fromY][fromX] = EmptySquare;
7556     } else if ((fromY == 3)
7557                && (toX != fromX)
7558                && gameInfo.variant != VariantXiangqi
7559                && gameInfo.variant != VariantBerolina
7560                && (board[fromY][fromX] == BlackPawn)
7561                && (board[toY][toX] == EmptySquare)) {
7562         board[fromY][fromX] = EmptySquare;
7563         board[toY][toX] = BlackPawn;
7564         captured = board[toY + 1][toX];
7565         board[toY + 1][toX] = EmptySquare;
7566     } else if ((fromY == 3)
7567                && (toX == fromX)
7568                && gameInfo.variant == VariantBerolina
7569                && (board[fromY][fromX] == BlackPawn)
7570                && (board[toY][toX] == EmptySquare)) {
7571         board[fromY][fromX] = EmptySquare;
7572         board[toY][toX] = BlackPawn;
7573         if(oldEP & EP_BEROLIN_A) {
7574                 captured = board[fromY][fromX-1];
7575                 board[fromY][fromX-1] = EmptySquare;
7576         }else{  captured = board[fromY][fromX+1];
7577                 board[fromY][fromX+1] = EmptySquare;
7578         }
7579     } else {
7580         board[toY][toX] = board[fromY][fromX];
7581         board[fromY][fromX] = EmptySquare;
7582     }
7583
7584     /* [HGM] now we promote for Shogi, if needed */
7585     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7586         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7587   }
7588
7589     if (gameInfo.holdingsWidth != 0) {
7590
7591       /* !!A lot more code needs to be written to support holdings  */
7592       /* [HGM] OK, so I have written it. Holdings are stored in the */
7593       /* penultimate board files, so they are automaticlly stored   */
7594       /* in the game history.                                       */
7595       if (fromY == DROP_RANK) {
7596         /* Delete from holdings, by decreasing count */
7597         /* and erasing image if necessary            */
7598         p = (int) fromX;
7599         if(p < (int) BlackPawn) { /* white drop */
7600              p -= (int)WhitePawn;
7601                  p = PieceToNumber((ChessSquare)p);
7602              if(p >= gameInfo.holdingsSize) p = 0;
7603              if(--board[p][BOARD_WIDTH-2] <= 0)
7604                   board[p][BOARD_WIDTH-1] = EmptySquare;
7605              if((int)board[p][BOARD_WIDTH-2] < 0)
7606                         board[p][BOARD_WIDTH-2] = 0;
7607         } else {                  /* black drop */
7608              p -= (int)BlackPawn;
7609                  p = PieceToNumber((ChessSquare)p);
7610              if(p >= gameInfo.holdingsSize) p = 0;
7611              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7612                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7613              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7614                         board[BOARD_HEIGHT-1-p][1] = 0;
7615         }
7616       }
7617       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7618           && gameInfo.variant != VariantBughouse        ) {
7619         /* [HGM] holdings: Add to holdings, if holdings exist */
7620         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7621                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7622                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7623         }
7624         p = (int) captured;
7625         if (p >= (int) BlackPawn) {
7626           p -= (int)BlackPawn;
7627           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7628                   /* in Shogi restore piece to its original  first */
7629                   captured = (ChessSquare) (DEMOTED captured);
7630                   p = DEMOTED p;
7631           }
7632           p = PieceToNumber((ChessSquare)p);
7633           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7634           board[p][BOARD_WIDTH-2]++;
7635           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7636         } else {
7637           p -= (int)WhitePawn;
7638           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7639                   captured = (ChessSquare) (DEMOTED captured);
7640                   p = DEMOTED p;
7641           }
7642           p = PieceToNumber((ChessSquare)p);
7643           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7644           board[BOARD_HEIGHT-1-p][1]++;
7645           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7646         }
7647       }
7648     } else if (gameInfo.variant == VariantAtomic) {
7649       if (captured != EmptySquare) {
7650         int y, x;
7651         for (y = toY-1; y <= toY+1; y++) {
7652           for (x = toX-1; x <= toX+1; x++) {
7653             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7654                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7655               board[y][x] = EmptySquare;
7656             }
7657           }
7658         }
7659         board[toY][toX] = EmptySquare;
7660       }
7661     }
7662     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7663         /* [HGM] Shogi promotions */
7664         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7665     }
7666
7667     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7668                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7669         // [HGM] superchess: take promotion piece out of holdings
7670         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7671         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7672             if(!--board[k][BOARD_WIDTH-2])
7673                 board[k][BOARD_WIDTH-1] = EmptySquare;
7674         } else {
7675             if(!--board[BOARD_HEIGHT-1-k][1])
7676                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7677         }
7678     }
7679
7680 }
7681
7682 /* Updates forwardMostMove */
7683 void
7684 MakeMove(fromX, fromY, toX, toY, promoChar)
7685      int fromX, fromY, toX, toY;
7686      int promoChar;
7687 {
7688 //    forwardMostMove++; // [HGM] bare: moved downstream
7689
7690     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7691         int timeLeft; static int lastLoadFlag=0; int king, piece;
7692         piece = boards[forwardMostMove][fromY][fromX];
7693         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7694         if(gameInfo.variant == VariantKnightmate)
7695             king += (int) WhiteUnicorn - (int) WhiteKing;
7696         if(forwardMostMove == 0) {
7697             if(blackPlaysFirst)
7698                 fprintf(serverMoves, "%s;", second.tidy);
7699             fprintf(serverMoves, "%s;", first.tidy);
7700             if(!blackPlaysFirst)
7701                 fprintf(serverMoves, "%s;", second.tidy);
7702         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7703         lastLoadFlag = loadFlag;
7704         // print base move
7705         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7706         // print castling suffix
7707         if( toY == fromY && piece == king ) {
7708             if(toX-fromX > 1)
7709                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7710             if(fromX-toX >1)
7711                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7712         }
7713         // e.p. suffix
7714         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7715              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7716              boards[forwardMostMove][toY][toX] == EmptySquare
7717              && fromX != toX )
7718                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7719         // promotion suffix
7720         if(promoChar != NULLCHAR)
7721                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7722         if(!loadFlag) {
7723             fprintf(serverMoves, "/%d/%d",
7724                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7725             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7726             else                      timeLeft = blackTimeRemaining/1000;
7727             fprintf(serverMoves, "/%d", timeLeft);
7728         }
7729         fflush(serverMoves);
7730     }
7731
7732     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7733       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7734                         0, 1);
7735       return;
7736     }
7737     if (commentList[forwardMostMove+1] != NULL) {
7738         free(commentList[forwardMostMove+1]);
7739         commentList[forwardMostMove+1] = NULL;
7740     }
7741     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7742     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7743     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7744     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7745     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7746     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7747     gameInfo.result = GameUnfinished;
7748     if (gameInfo.resultDetails != NULL) {
7749         free(gameInfo.resultDetails);
7750         gameInfo.resultDetails = NULL;
7751     }
7752     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7753                               moveList[forwardMostMove - 1]);
7754     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7755                              PosFlags(forwardMostMove - 1),
7756                              fromY, fromX, toY, toX, promoChar,
7757                              parseList[forwardMostMove - 1]);
7758     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7759       case MT_NONE:
7760       case MT_STALEMATE:
7761       default:
7762         break;
7763       case MT_CHECK:
7764         if(gameInfo.variant != VariantShogi)
7765             strcat(parseList[forwardMostMove - 1], "+");
7766         break;
7767       case MT_CHECKMATE:
7768       case MT_STAINMATE:
7769         strcat(parseList[forwardMostMove - 1], "#");
7770         break;
7771     }
7772     if (appData.debugMode) {
7773         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7774     }
7775
7776 }
7777
7778 /* Updates currentMove if not pausing */
7779 void
7780 ShowMove(fromX, fromY, toX, toY)
7781 {
7782     int instant = (gameMode == PlayFromGameFile) ?
7783         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7784
7785     if(appData.noGUI) return;
7786
7787     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7788       {
7789         if (!instant)
7790           {
7791             if (forwardMostMove == currentMove + 1)
7792               {
7793 //TODO
7794 //              AnimateMove(boards[forwardMostMove - 1],
7795 //                          fromX, fromY, toX, toY);
7796               }
7797             if (appData.highlightLastMove)
7798               {
7799                 SetHighlights(fromX, fromY, toX, toY);
7800               }
7801           }
7802         currentMove = forwardMostMove;
7803     }
7804
7805     if (instant) return;
7806
7807     DisplayMove(currentMove - 1);
7808     DrawPosition(FALSE, boards[currentMove]);
7809     DisplayBothClocks();
7810     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7811
7812     return;
7813 }
7814
7815 void SendEgtPath(ChessProgramState *cps)
7816 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7817         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7818
7819         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7820
7821         while(*p) {
7822             char c, *q = name+1, *r, *s;
7823
7824             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7825             while(*p && *p != ',') *q++ = *p++;
7826             *q++ = ':'; *q = 0;
7827             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7828                 strcmp(name, ",nalimov:") == 0 ) {
7829                 // take nalimov path from the menu-changeable option first, if it is defined
7830                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7831                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7832             } else
7833             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7834                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7835                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7836                 s = r = StrStr(s, ":") + 1; // beginning of path info
7837                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7838                 c = *r; *r = 0;             // temporarily null-terminate path info
7839                     *--q = 0;               // strip of trailig ':' from name
7840                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7841                 *r = c;
7842                 SendToProgram(buf,cps);     // send egtbpath command for this format
7843             }
7844             if(*p == ',') p++; // read away comma to position for next format name
7845         }
7846 }
7847
7848 void
7849 InitChessProgram(cps, setup)
7850      ChessProgramState *cps;
7851      int setup; /* [HGM] needed to setup FRC opening position */
7852 {
7853     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7854     if (appData.noChessProgram) return;
7855     hintRequested = FALSE;
7856     bookRequested = FALSE;
7857
7858     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7859     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7860     if(cps->memSize) { /* [HGM] memory */
7861         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7862         SendToProgram(buf, cps);
7863     }
7864     SendEgtPath(cps); /* [HGM] EGT */
7865     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7866         sprintf(buf, "cores %d\n", appData.smpCores);
7867         SendToProgram(buf, cps);
7868     }
7869
7870     SendToProgram(cps->initString, cps);
7871     if (gameInfo.variant != VariantNormal &&
7872         gameInfo.variant != VariantLoadable
7873         /* [HGM] also send variant if board size non-standard */
7874         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7875                                             ) {
7876       char *v = VariantName(gameInfo.variant);
7877       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7878         /* [HGM] in protocol 1 we have to assume all variants valid */
7879         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7880         DisplayFatalError(buf, 0, 1);
7881         return;
7882       }
7883
7884       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7885       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7886       if( gameInfo.variant == VariantXiangqi )
7887            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7888       if( gameInfo.variant == VariantShogi )
7889            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7890       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7891            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7892       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7893                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7894            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7895       if( gameInfo.variant == VariantCourier )
7896            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7897       if( gameInfo.variant == VariantSuper )
7898            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7899       if( gameInfo.variant == VariantGreat )
7900            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7901
7902       if(overruled) {
7903            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7904                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7905            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7906            if(StrStr(cps->variants, b) == NULL) {
7907                // specific sized variant not known, check if general sizing allowed
7908                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7909                    if(StrStr(cps->variants, "boardsize") == NULL) {
7910                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7911                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7912                        DisplayFatalError(buf, 0, 1);
7913                        return;
7914                    }
7915                    /* [HGM] here we really should compare with the maximum supported board size */
7916                }
7917            }
7918       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7919       sprintf(buf, "variant %s\n", b);
7920       SendToProgram(buf, cps);
7921     }
7922     currentlyInitializedVariant = gameInfo.variant;
7923
7924     /* [HGM] send opening position in FRC to first engine */
7925     if(setup) {
7926           SendToProgram("force\n", cps);
7927           SendBoard(cps, 0);
7928           /* engine is now in force mode! Set flag to wake it up after first move. */
7929           setboardSpoiledMachineBlack = 1;
7930     }
7931
7932     if (cps->sendICS) {
7933       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7934       SendToProgram(buf, cps);
7935     }
7936     cps->maybeThinking = FALSE;
7937     cps->offeredDraw = 0;
7938     if (!appData.icsActive) {
7939         SendTimeControl(cps, movesPerSession, timeControl,
7940                         timeIncrement, appData.searchDepth,
7941                         searchTime);
7942     }
7943     if (appData.showThinking
7944         // [HGM] thinking: four options require thinking output to be sent
7945         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7946                                 ) {
7947         SendToProgram("post\n", cps);
7948     }
7949     SendToProgram("hard\n", cps);
7950     if (!appData.ponderNextMove) {
7951         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7952            it without being sure what state we are in first.  "hard"
7953            is not a toggle, so that one is OK.
7954          */
7955         SendToProgram("easy\n", cps);
7956     }
7957     if (cps->usePing) {
7958       sprintf(buf, "ping %d\n", ++cps->lastPing);
7959       SendToProgram(buf, cps);
7960     }
7961     cps->initDone = TRUE;
7962 }
7963
7964
7965 void
7966 StartChessProgram(cps)
7967      ChessProgramState *cps;
7968 {
7969     char buf[MSG_SIZ];
7970     int err;
7971
7972     if (appData.noChessProgram) return;
7973     cps->initDone = FALSE;
7974
7975     if (strcmp(cps->host, "localhost") == 0) {
7976         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7977     } else if (*appData.remoteShell == NULLCHAR) {
7978         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7979     } else {
7980         if (*appData.remoteUser == NULLCHAR) {
7981           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7982                     cps->program);
7983         } else {
7984           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7985                     cps->host, appData.remoteUser, cps->program);
7986         }
7987         err = StartChildProcess(buf, "", &cps->pr);
7988     }
7989
7990     if (err != 0) {
7991         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7992         DisplayFatalError(buf, err, 1);
7993         cps->pr = NoProc;
7994         cps->isr = NULL;
7995         return;
7996     }
7997
7998     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7999     if (cps->protocolVersion > 1) {
8000       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8001       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8002       cps->comboCnt = 0;  //                and values of combo boxes
8003       SendToProgram(buf, cps);
8004     } else {
8005       SendToProgram("xboard\n", cps);
8006     }
8007 }
8008
8009
8010 void
8011 TwoMachinesEventIfReady P((void))
8012 {
8013   if (first.lastPing != first.lastPong) {
8014     DisplayMessage("", _("Waiting for first chess program"));
8015     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8016     return;
8017   }
8018   if (second.lastPing != second.lastPong) {
8019     DisplayMessage("", _("Waiting for second chess program"));
8020     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8021     return;
8022   }
8023   ThawUI();
8024   TwoMachinesEvent();
8025 }
8026
8027 void
8028 NextMatchGame P((void))
8029 {
8030     int index; /* [HGM] autoinc: step load index during match */
8031     Reset(FALSE, TRUE);
8032     if (*appData.loadGameFile != NULLCHAR) {
8033         index = appData.loadGameIndex;
8034         if(index < 0) { // [HGM] autoinc
8035             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8036             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8037         }
8038         LoadGameFromFile(appData.loadGameFile,
8039                          index,
8040                          appData.loadGameFile, FALSE);
8041     } else if (*appData.loadPositionFile != NULLCHAR) {
8042         index = appData.loadPositionIndex;
8043         if(index < 0) { // [HGM] autoinc
8044             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8045             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8046         }
8047         LoadPositionFromFile(appData.loadPositionFile,
8048                              index,
8049                              appData.loadPositionFile);
8050     }
8051     TwoMachinesEventIfReady();
8052 }
8053
8054 void UserAdjudicationEvent( int result )
8055 {
8056     ChessMove gameResult = GameIsDrawn;
8057
8058     if( result > 0 ) {
8059         gameResult = WhiteWins;
8060     }
8061     else if( result < 0 ) {
8062         gameResult = BlackWins;
8063     }
8064
8065     if( gameMode == TwoMachinesPlay ) {
8066         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8067     }
8068 }
8069
8070
8071 // [HGM] save: calculate checksum of game to make games easily identifiable
8072 int StringCheckSum(char *s)
8073 {
8074         int i = 0;
8075         if(s==NULL) return 0;
8076         while(*s) i = i*259 + *s++;
8077         return i;
8078 }
8079
8080 int GameCheckSum()
8081 {
8082         int i, sum=0;
8083         for(i=backwardMostMove; i<forwardMostMove; i++) {
8084                 sum += pvInfoList[i].depth;
8085                 sum += StringCheckSum(parseList[i]);
8086                 sum += StringCheckSum(commentList[i]);
8087                 sum *= 261;
8088         }
8089         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8090         return sum + StringCheckSum(commentList[i]);
8091 } // end of save patch
8092
8093 void
8094 GameEnds(result, resultDetails, whosays)
8095      ChessMove result;
8096      char *resultDetails;
8097      int whosays;
8098 {
8099     GameMode nextGameMode;
8100     int isIcsGame;
8101     char buf[MSG_SIZ];
8102
8103     if(endingGame) return; /* [HGM] crash: forbid recursion */
8104     endingGame = 1;
8105
8106     if (appData.debugMode) {
8107       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8108               result, resultDetails ? resultDetails : "(null)", whosays);
8109     }
8110
8111     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8112         /* If we are playing on ICS, the server decides when the
8113            game is over, but the engine can offer to draw, claim
8114            a draw, or resign.
8115          */
8116 #if ZIPPY
8117         if (appData.zippyPlay && first.initDone) {
8118             if (result == GameIsDrawn) {
8119                 /* In case draw still needs to be claimed */
8120                 SendToICS(ics_prefix);
8121                 SendToICS("draw\n");
8122             } else if (StrCaseStr(resultDetails, "resign")) {
8123                 SendToICS(ics_prefix);
8124                 SendToICS("resign\n");
8125             }
8126         }
8127 #endif
8128         endingGame = 0; /* [HGM] crash */
8129         return;
8130     }
8131
8132     /* If we're loading the game from a file, stop */
8133     if (whosays == GE_FILE) {
8134       (void) StopLoadGameTimer();
8135       gameFileFP = NULL;
8136     }
8137
8138     /* Cancel draw offers */
8139     first.offeredDraw = second.offeredDraw = 0;
8140
8141     /* If this is an ICS game, only ICS can really say it's done;
8142        if not, anyone can. */
8143     isIcsGame = (gameMode == IcsPlayingWhite ||
8144                  gameMode == IcsPlayingBlack ||
8145                  gameMode == IcsObserving    ||
8146                  gameMode == IcsExamining);
8147
8148     if (!isIcsGame || whosays == GE_ICS) {
8149         /* OK -- not an ICS game, or ICS said it was done */
8150         StopClocks();
8151         if (!isIcsGame && !appData.noChessProgram)
8152           SetUserThinkingEnables();
8153
8154         /* [HGM] if a machine claims the game end we verify this claim */
8155         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8156             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8157                 char claimer;
8158                 ChessMove trueResult = (ChessMove) -1;
8159
8160                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8161                                             first.twoMachinesColor[0] :
8162                                             second.twoMachinesColor[0] ;
8163
8164                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8165                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8166                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8167                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8168                 } else
8169                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8170                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8171                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8172                 } else
8173                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8174                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8175                 }
8176
8177                 // now verify win claims, but not in drop games, as we don't understand those yet
8178                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8179                                                  || gameInfo.variant == VariantGreat) &&
8180                     (result == WhiteWins && claimer == 'w' ||
8181                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8182                       if (appData.debugMode) {
8183                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8184                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8185                       }
8186                       if(result != trueResult) {
8187                               sprintf(buf, "False win claim: '%s'", resultDetails);
8188                               result = claimer == 'w' ? BlackWins : WhiteWins;
8189                               resultDetails = buf;
8190                       }
8191                 } else
8192                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8193                     && (forwardMostMove <= backwardMostMove ||
8194                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8195                         (claimer=='b')==(forwardMostMove&1))
8196                                                                                   ) {
8197                       /* [HGM] verify: draws that were not flagged are false claims */
8198                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8199                       result = claimer == 'w' ? BlackWins : WhiteWins;
8200                       resultDetails = buf;
8201                 }
8202                 /* (Claiming a loss is accepted no questions asked!) */
8203             }
8204
8205             /* [HGM] bare: don't allow bare King to win */
8206             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8207                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8208                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8209                && result != GameIsDrawn)
8210             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8211                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8212                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8213                         if(p >= 0 && p <= (int)WhiteKing) k++;
8214                 }
8215                 if (appData.debugMode) {
8216                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8217                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8218                 }
8219                 if(k <= 1) {
8220                         result = GameIsDrawn;
8221                         sprintf(buf, "%s but bare king", resultDetails);
8222                         resultDetails = buf;
8223                 }
8224             }
8225         }
8226
8227         if(serverMoves != NULL && !loadFlag) { char c = '=';
8228             if(result==WhiteWins) c = '+';
8229             if(result==BlackWins) c = '-';
8230             if(resultDetails != NULL)
8231                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8232         }
8233         if (resultDetails != NULL) {
8234             gameInfo.result = result;
8235             gameInfo.resultDetails = StrSave(resultDetails);
8236
8237             /* display last move only if game was not loaded from file */
8238             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8239                 DisplayMove(currentMove - 1);
8240
8241             if (forwardMostMove != 0) {
8242                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8243                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8244                                                                 ) {
8245                     if (*appData.saveGameFile != NULLCHAR) {
8246                         SaveGameToFile(appData.saveGameFile, TRUE);
8247                     } else if (appData.autoSaveGames) {
8248                         AutoSaveGame();
8249                     }
8250                     if (*appData.savePositionFile != NULLCHAR) {
8251                         SavePositionToFile(appData.savePositionFile);
8252                     }
8253                 }
8254             }
8255
8256             /* Tell program how game ended in case it is learning */
8257             /* [HGM] Moved this to after saving the PGN, just in case */
8258             /* engine died and we got here through time loss. In that */
8259             /* case we will get a fatal error writing the pipe, which */
8260             /* would otherwise lose us the PGN.                       */
8261             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8262             /* output during GameEnds should never be fatal anymore   */
8263             if (gameMode == MachinePlaysWhite ||
8264                 gameMode == MachinePlaysBlack ||
8265                 gameMode == TwoMachinesPlay ||
8266                 gameMode == IcsPlayingWhite ||
8267                 gameMode == IcsPlayingBlack ||
8268                 gameMode == BeginningOfGame) {
8269                 char buf[MSG_SIZ];
8270                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8271                         resultDetails);
8272                 if (first.pr != NoProc) {
8273                     SendToProgram(buf, &first);
8274                 }
8275                 if (second.pr != NoProc &&
8276                     gameMode == TwoMachinesPlay) {
8277                     SendToProgram(buf, &second);
8278                 }
8279             }
8280         }
8281
8282         if (appData.icsActive) {
8283             if (appData.quietPlay &&
8284                 (gameMode == IcsPlayingWhite ||
8285                  gameMode == IcsPlayingBlack)) {
8286                 SendToICS(ics_prefix);
8287                 SendToICS("set shout 1\n");
8288             }
8289             nextGameMode = IcsIdle;
8290             ics_user_moved = FALSE;
8291             /* clean up premove.  It's ugly when the game has ended and the
8292              * premove highlights are still on the board.
8293              */
8294             if (gotPremove) {
8295               gotPremove = FALSE;
8296               ClearPremoveHighlights();
8297               DrawPosition(FALSE, boards[currentMove]);
8298             }
8299             if (whosays == GE_ICS) {
8300                 switch (result) {
8301                 case WhiteWins:
8302                     if (gameMode == IcsPlayingWhite)
8303                         PlayIcsWinSound();
8304                     else if(gameMode == IcsPlayingBlack)
8305                         PlayIcsLossSound();
8306                     break;
8307                 case BlackWins:
8308                     if (gameMode == IcsPlayingBlack)
8309                         PlayIcsWinSound();
8310                     else if(gameMode == IcsPlayingWhite)
8311                         PlayIcsLossSound();
8312                     break;
8313                 case GameIsDrawn:
8314                     PlayIcsDrawSound();
8315                     break;
8316                 default:
8317                     PlayIcsUnfinishedSound();
8318                 }
8319             }
8320         } else if (gameMode == EditGame ||
8321                    gameMode == PlayFromGameFile ||
8322                    gameMode == AnalyzeMode ||
8323                    gameMode == AnalyzeFile) {
8324             nextGameMode = gameMode;
8325         } else {
8326             nextGameMode = EndOfGame;
8327         }
8328         pausing = FALSE;
8329         ModeHighlight();
8330     } else {
8331         nextGameMode = gameMode;
8332     }
8333
8334     if (appData.noChessProgram) {
8335         gameMode = nextGameMode;
8336         ModeHighlight();
8337         endingGame = 0; /* [HGM] crash */
8338         return;
8339     }
8340
8341     if (first.reuse) {
8342         /* Put first chess program into idle state */
8343         if (first.pr != NoProc &&
8344             (gameMode == MachinePlaysWhite ||
8345              gameMode == MachinePlaysBlack ||
8346              gameMode == TwoMachinesPlay ||
8347              gameMode == IcsPlayingWhite ||
8348              gameMode == IcsPlayingBlack ||
8349              gameMode == BeginningOfGame)) {
8350             SendToProgram("force\n", &first);
8351             if (first.usePing) {
8352               char buf[MSG_SIZ];
8353               sprintf(buf, "ping %d\n", ++first.lastPing);
8354               SendToProgram(buf, &first);
8355             }
8356         }
8357     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8358         /* Kill off first chess program */
8359         if (first.isr != NULL)
8360           RemoveInputSource(first.isr);
8361         first.isr = NULL;
8362
8363         if (first.pr != NoProc) {
8364             ExitAnalyzeMode();
8365             DoSleep( appData.delayBeforeQuit );
8366             SendToProgram("quit\n", &first);
8367             DoSleep( appData.delayAfterQuit );
8368             DestroyChildProcess(first.pr, first.useSigterm);
8369         }
8370         first.pr = NoProc;
8371     }
8372     if (second.reuse) {
8373         /* Put second chess program into idle state */
8374         if (second.pr != NoProc &&
8375             gameMode == TwoMachinesPlay) {
8376             SendToProgram("force\n", &second);
8377             if (second.usePing) {
8378               char buf[MSG_SIZ];
8379               sprintf(buf, "ping %d\n", ++second.lastPing);
8380               SendToProgram(buf, &second);
8381             }
8382         }
8383     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8384         /* Kill off second chess program */
8385         if (second.isr != NULL)
8386           RemoveInputSource(second.isr);
8387         second.isr = NULL;
8388
8389         if (second.pr != NoProc) {
8390             DoSleep( appData.delayBeforeQuit );
8391             SendToProgram("quit\n", &second);
8392             DoSleep( appData.delayAfterQuit );
8393             DestroyChildProcess(second.pr, second.useSigterm);
8394         }
8395         second.pr = NoProc;
8396     }
8397
8398     if (matchMode && gameMode == TwoMachinesPlay) {
8399         switch (result) {
8400         case WhiteWins:
8401           if (first.twoMachinesColor[0] == 'w') {
8402             first.matchWins++;
8403           } else {
8404             second.matchWins++;
8405           }
8406           break;
8407         case BlackWins:
8408           if (first.twoMachinesColor[0] == 'b') {
8409             first.matchWins++;
8410           } else {
8411             second.matchWins++;
8412           }
8413           break;
8414         default:
8415           break;
8416         }
8417         if (matchGame < appData.matchGames) {
8418             char *tmp;
8419             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8420                 tmp = first.twoMachinesColor;
8421                 first.twoMachinesColor = second.twoMachinesColor;
8422                 second.twoMachinesColor = tmp;
8423             }
8424             gameMode = nextGameMode;
8425             matchGame++;
8426             if(appData.matchPause>10000 || appData.matchPause<10)
8427                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8428             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8429             endingGame = 0; /* [HGM] crash */
8430             return;
8431         } else {
8432             char buf[MSG_SIZ];
8433             gameMode = nextGameMode;
8434             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8435                     first.tidy, second.tidy,
8436                     first.matchWins, second.matchWins,
8437                     appData.matchGames - (first.matchWins + second.matchWins));
8438             DisplayFatalError(buf, 0, 0);
8439         }
8440     }
8441     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8442         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8443       ExitAnalyzeMode();
8444     gameMode = nextGameMode;
8445     ModeHighlight();
8446     endingGame = 0;  /* [HGM] crash */
8447 }
8448
8449 /* Assumes program was just initialized (initString sent).
8450    Leaves program in force mode. */
8451 void
8452 FeedMovesToProgram(cps, upto)
8453      ChessProgramState *cps;
8454      int upto;
8455 {
8456     int i;
8457
8458     if (appData.debugMode)
8459       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8460               startedFromSetupPosition ? "position and " : "",
8461               backwardMostMove, upto, cps->which);
8462     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8463         // [HGM] variantswitch: make engine aware of new variant
8464         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8465                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8466         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8467         SendToProgram(buf, cps);
8468         currentlyInitializedVariant = gameInfo.variant;
8469     }
8470     SendToProgram("force\n", cps);
8471     if (startedFromSetupPosition) {
8472         SendBoard(cps, backwardMostMove);
8473     if (appData.debugMode) {
8474         fprintf(debugFP, "feedMoves\n");
8475     }
8476     }
8477     for (i = backwardMostMove; i < upto; i++) {
8478         SendMoveToProgram(i, cps);
8479     }
8480 }
8481
8482
8483 void
8484 ResurrectChessProgram()
8485 {
8486      /* The chess program may have exited.
8487         If so, restart it and feed it all the moves made so far. */
8488
8489     if (appData.noChessProgram || first.pr != NoProc) return;
8490
8491     StartChessProgram(&first);
8492     InitChessProgram(&first, FALSE);
8493     FeedMovesToProgram(&first, currentMove);
8494
8495     if (!first.sendTime) {
8496         /* can't tell gnuchess what its clock should read,
8497            so we bow to its notion. */
8498         ResetClocks();
8499         timeRemaining[0][currentMove] = whiteTimeRemaining;
8500         timeRemaining[1][currentMove] = blackTimeRemaining;
8501     }
8502
8503     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8504                 appData.icsEngineAnalyze) && first.analysisSupport) {
8505       SendToProgram("analyze\n", &first);
8506       first.analyzing = TRUE;
8507     }
8508 }
8509
8510 /*
8511  * Button procedures
8512  */
8513 void
8514 Reset(redraw, init)
8515      int redraw, init;
8516 {
8517     int i;
8518
8519     if (appData.debugMode) {
8520         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8521                 redraw, init, gameMode);
8522     }
8523     CleanupTail(); // [HGM] vari: delete any stored variations
8524     pausing = pauseExamInvalid = FALSE;
8525     startedFromSetupPosition = blackPlaysFirst = FALSE;
8526     firstMove = TRUE;
8527     whiteFlag = blackFlag = FALSE;
8528     userOfferedDraw = FALSE;
8529     hintRequested = bookRequested = FALSE;
8530     first.maybeThinking = FALSE;
8531     second.maybeThinking = FALSE;
8532     first.bookSuspend = FALSE; // [HGM] book
8533     second.bookSuspend = FALSE;
8534     thinkOutput[0] = NULLCHAR;
8535     lastHint[0] = NULLCHAR;
8536     ClearGameInfo(&gameInfo);
8537     gameInfo.variant = StringToVariant(appData.variant);
8538     ics_user_moved = ics_clock_paused = FALSE;
8539     ics_getting_history = H_FALSE;
8540     ics_gamenum = -1;
8541     white_holding[0] = black_holding[0] = NULLCHAR;
8542     ClearProgramStats();
8543     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8544
8545     ResetFrontEnd();
8546     ClearHighlights();
8547     flipView = appData.flipView;
8548     ClearPremoveHighlights();
8549     gotPremove = FALSE;
8550     alarmSounded = FALSE;
8551
8552     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8553     if(appData.serverMovesName != NULL) {
8554         /* [HGM] prepare to make moves file for broadcasting */
8555         clock_t t = clock();
8556         if(serverMoves != NULL) fclose(serverMoves);
8557         serverMoves = fopen(appData.serverMovesName, "r");
8558         if(serverMoves != NULL) {
8559             fclose(serverMoves);
8560             /* delay 15 sec before overwriting, so all clients can see end */
8561             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8562         }
8563         serverMoves = fopen(appData.serverMovesName, "w");
8564     }
8565
8566     ExitAnalyzeMode();
8567     gameMode = BeginningOfGame;
8568     ModeHighlight();
8569
8570     if(appData.icsActive) gameInfo.variant = VariantNormal;
8571     currentMove = forwardMostMove = backwardMostMove = 0;
8572     InitPosition(redraw);
8573     for (i = 0; i < MAX_MOVES; i++) {
8574         if (commentList[i] != NULL) {
8575             free(commentList[i]);
8576             commentList[i] = NULL;
8577         }
8578     }
8579
8580     ResetClocks();
8581     timeRemaining[0][0] = whiteTimeRemaining;
8582     timeRemaining[1][0] = blackTimeRemaining;
8583     if (first.pr == NULL) {
8584         StartChessProgram(&first);
8585     }
8586     if (init) {
8587             InitChessProgram(&first, startedFromSetupPosition);
8588     }
8589
8590     DisplayTitle("");
8591     DisplayMessage("", "");
8592     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8593     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8594     return;
8595 }
8596
8597 void
8598 AutoPlayGameLoop()
8599 {
8600     for (;;) {
8601         if (!AutoPlayOneMove())
8602           return;
8603         if (matchMode || appData.timeDelay == 0)
8604           continue;
8605         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8606           return;
8607         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8608         break;
8609     }
8610 }
8611
8612
8613 int
8614 AutoPlayOneMove()
8615 {
8616     int fromX, fromY, toX, toY;
8617
8618     if (appData.debugMode) {
8619       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8620     }
8621
8622     if (gameMode != PlayFromGameFile)
8623       return FALSE;
8624
8625     if (currentMove >= forwardMostMove) {
8626       gameMode = EditGame;
8627       ModeHighlight();
8628
8629       /* [AS] Clear current move marker at the end of a game */
8630       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8631
8632       return FALSE;
8633     }
8634
8635     toX = moveList[currentMove][2] - AAA;
8636     toY = moveList[currentMove][3] - ONE;
8637
8638     if (moveList[currentMove][1] == '@') {
8639         if (appData.highlightLastMove) {
8640             SetHighlights(-1, -1, toX, toY);
8641         }
8642     } else {
8643         fromX = moveList[currentMove][0] - AAA;
8644         fromY = moveList[currentMove][1] - ONE;
8645
8646         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8647
8648         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8649
8650         if (appData.highlightLastMove) {
8651             SetHighlights(fromX, fromY, toX, toY);
8652         }
8653     }
8654     DisplayMove(currentMove);
8655     SendMoveToProgram(currentMove++, &first);
8656     DisplayBothClocks();
8657     DrawPosition(FALSE, boards[currentMove]);
8658     // [HGM] PV info: always display, routine tests if empty
8659     DisplayComment(currentMove - 1, commentList[currentMove]);
8660     return TRUE;
8661 }
8662
8663
8664 int
8665 LoadGameOneMove(readAhead)
8666      ChessMove readAhead;
8667 {
8668     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8669     char promoChar = NULLCHAR;
8670     ChessMove moveType;
8671     char move[MSG_SIZ];
8672     char *p, *q;
8673
8674     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8675         gameMode != AnalyzeMode && gameMode != Training) {
8676         gameFileFP = NULL;
8677         return FALSE;
8678     }
8679
8680     yyboardindex = forwardMostMove;
8681     if (readAhead != (ChessMove)0) {
8682       moveType = readAhead;
8683     } else {
8684       if (gameFileFP == NULL)
8685           return FALSE;
8686       moveType = (ChessMove) yylex();
8687     }
8688
8689     done = FALSE;
8690     switch (moveType) {
8691       case Comment:
8692         if (appData.debugMode)
8693           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8694         p = yy_text;
8695
8696         /* append the comment but don't display it */
8697         AppendComment(currentMove, p, FALSE);
8698         return TRUE;
8699
8700       case WhiteCapturesEnPassant:
8701       case BlackCapturesEnPassant:
8702       case WhitePromotionChancellor:
8703       case BlackPromotionChancellor:
8704       case WhitePromotionArchbishop:
8705       case BlackPromotionArchbishop:
8706       case WhitePromotionCentaur:
8707       case BlackPromotionCentaur:
8708       case WhitePromotionQueen:
8709       case BlackPromotionQueen:
8710       case WhitePromotionRook:
8711       case BlackPromotionRook:
8712       case WhitePromotionBishop:
8713       case BlackPromotionBishop:
8714       case WhitePromotionKnight:
8715       case BlackPromotionKnight:
8716       case WhitePromotionKing:
8717       case BlackPromotionKing:
8718       case NormalMove:
8719       case WhiteKingSideCastle:
8720       case WhiteQueenSideCastle:
8721       case BlackKingSideCastle:
8722       case BlackQueenSideCastle:
8723       case WhiteKingSideCastleWild:
8724       case WhiteQueenSideCastleWild:
8725       case BlackKingSideCastleWild:
8726       case BlackQueenSideCastleWild:
8727       /* PUSH Fabien */
8728       case WhiteHSideCastleFR:
8729       case WhiteASideCastleFR:
8730       case BlackHSideCastleFR:
8731       case BlackASideCastleFR:
8732       /* POP Fabien */
8733         if (appData.debugMode)
8734           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8735         fromX = currentMoveString[0] - AAA;
8736         fromY = currentMoveString[1] - ONE;
8737         toX = currentMoveString[2] - AAA;
8738         toY = currentMoveString[3] - ONE;
8739         promoChar = currentMoveString[4];
8740         break;
8741
8742       case WhiteDrop:
8743       case BlackDrop:
8744         if (appData.debugMode)
8745           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8746         fromX = moveType == WhiteDrop ?
8747           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8748         (int) CharToPiece(ToLower(currentMoveString[0]));
8749         fromY = DROP_RANK;
8750         toX = currentMoveString[2] - AAA;
8751         toY = currentMoveString[3] - ONE;
8752         break;
8753
8754       case WhiteWins:
8755       case BlackWins:
8756       case GameIsDrawn:
8757       case GameUnfinished:
8758         if (appData.debugMode)
8759           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8760         p = strchr(yy_text, '{');
8761         if (p == NULL) p = strchr(yy_text, '(');
8762         if (p == NULL) {
8763             p = yy_text;
8764             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8765         } else {
8766             q = strchr(p, *p == '{' ? '}' : ')');
8767             if (q != NULL) *q = NULLCHAR;
8768             p++;
8769         }
8770         GameEnds(moveType, p, GE_FILE);
8771         done = TRUE;
8772         if (cmailMsgLoaded) {
8773             ClearHighlights();
8774             flipView = WhiteOnMove(currentMove);
8775             if (moveType == GameUnfinished) flipView = !flipView;
8776             if (appData.debugMode)
8777               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8778         }
8779         break;
8780
8781       case (ChessMove) 0:       /* end of file */
8782         if (appData.debugMode)
8783           fprintf(debugFP, "Parser hit end of file\n");
8784         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8785           case MT_NONE:
8786           case MT_CHECK:
8787             break;
8788           case MT_CHECKMATE:
8789           case MT_STAINMATE:
8790             if (WhiteOnMove(currentMove)) {
8791                 GameEnds(BlackWins, "Black mates", GE_FILE);
8792             } else {
8793                 GameEnds(WhiteWins, "White mates", GE_FILE);
8794             }
8795             break;
8796           case MT_STALEMATE:
8797             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8798             break;
8799         }
8800         done = TRUE;
8801         break;
8802
8803       case MoveNumberOne:
8804         if (lastLoadGameStart == GNUChessGame) {
8805             /* GNUChessGames have numbers, but they aren't move numbers */
8806             if (appData.debugMode)
8807               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8808                       yy_text, (int) moveType);
8809             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8810         }
8811         /* else fall thru */
8812
8813       case XBoardGame:
8814       case GNUChessGame:
8815       case PGNTag:
8816         /* Reached start of next game in file */
8817         if (appData.debugMode)
8818           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8819         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8820           case MT_NONE:
8821           case MT_CHECK:
8822             break;
8823           case MT_CHECKMATE:
8824           case MT_STAINMATE:
8825             if (WhiteOnMove(currentMove)) {
8826                 GameEnds(BlackWins, "Black mates", GE_FILE);
8827             } else {
8828                 GameEnds(WhiteWins, "White mates", GE_FILE);
8829             }
8830             break;
8831           case MT_STALEMATE:
8832             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8833             break;
8834         }
8835         done = TRUE;
8836         break;
8837
8838       case PositionDiagram:     /* should not happen; ignore */
8839       case ElapsedTime:         /* ignore */
8840       case NAG:                 /* ignore */
8841         if (appData.debugMode)
8842           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8843                   yy_text, (int) moveType);
8844         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8845
8846       case IllegalMove:
8847         if (appData.testLegality) {
8848             if (appData.debugMode)
8849               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8850             sprintf(move, _("Illegal move: %d.%s%s"),
8851                     (forwardMostMove / 2) + 1,
8852                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8853             DisplayError(move, 0);
8854             done = TRUE;
8855         } else {
8856             if (appData.debugMode)
8857               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8858                       yy_text, currentMoveString);
8859             fromX = currentMoveString[0] - AAA;
8860             fromY = currentMoveString[1] - ONE;
8861             toX = currentMoveString[2] - AAA;
8862             toY = currentMoveString[3] - ONE;
8863             promoChar = currentMoveString[4];
8864         }
8865         break;
8866
8867       case AmbiguousMove:
8868         if (appData.debugMode)
8869           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8870         sprintf(move, _("Ambiguous move: %d.%s%s"),
8871                 (forwardMostMove / 2) + 1,
8872                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8873         DisplayError(move, 0);
8874         done = TRUE;
8875         break;
8876
8877       default:
8878       case ImpossibleMove:
8879         if (appData.debugMode)
8880           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8881         sprintf(move, _("Illegal move: %d.%s%s"),
8882                 (forwardMostMove / 2) + 1,
8883                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8884         DisplayError(move, 0);
8885         done = TRUE;
8886         break;
8887     }
8888
8889     if (done) {
8890         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8891             DrawPosition(FALSE, boards[currentMove]);
8892             DisplayBothClocks();
8893             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8894               DisplayComment(currentMove - 1, commentList[currentMove]);
8895         }
8896         (void) StopLoadGameTimer();
8897         gameFileFP = NULL;
8898         cmailOldMove = forwardMostMove;
8899         return FALSE;
8900     } else {
8901         /* currentMoveString is set as a side-effect of yylex */
8902         strcat(currentMoveString, "\n");
8903         strcpy(moveList[forwardMostMove], currentMoveString);
8904
8905         thinkOutput[0] = NULLCHAR;
8906         MakeMove(fromX, fromY, toX, toY, promoChar);
8907         currentMove = forwardMostMove;
8908         return TRUE;
8909     }
8910 }
8911
8912 /* Load the nth game from the given file */
8913 int
8914 LoadGameFromFile(filename, n, title, useList)
8915      char *filename;
8916      int n;
8917      char *title;
8918      /*Boolean*/ int useList;
8919 {
8920     FILE *f;
8921     char buf[MSG_SIZ];
8922
8923     if (strcmp(filename, "-") == 0) {
8924         f = stdin;
8925         title = "stdin";
8926     } else {
8927         f = fopen(filename, "rb");
8928         if (f == NULL) {
8929           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8930             DisplayError(buf, errno);
8931             return FALSE;
8932         }
8933     }
8934     if (fseek(f, 0, 0) == -1) {
8935         /* f is not seekable; probably a pipe */
8936         useList = FALSE;
8937     }
8938     if (useList && n == 0) {
8939         int error = GameListBuild(f);
8940         if (error) {
8941             DisplayError(_("Cannot build game list"), error);
8942         } else if (!ListEmpty(&gameList) &&
8943                    ((ListGame *) gameList.tailPred)->number > 1) {
8944           // TODO convert to GTK
8945           //        GameListPopUp(f, title);
8946             return TRUE;
8947         }
8948         GameListDestroy();
8949         n = 1;
8950     }
8951     if (n == 0) n = 1;
8952     return LoadGame(f, n, title, FALSE);
8953 }
8954
8955
8956 void
8957 MakeRegisteredMove()
8958 {
8959     int fromX, fromY, toX, toY;
8960     char promoChar;
8961     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8962         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8963           case CMAIL_MOVE:
8964           case CMAIL_DRAW:
8965             if (appData.debugMode)
8966               fprintf(debugFP, "Restoring %s for game %d\n",
8967                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8968
8969             thinkOutput[0] = NULLCHAR;
8970             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8971             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8972             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8973             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8974             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8975             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8976             MakeMove(fromX, fromY, toX, toY, promoChar);
8977             ShowMove(fromX, fromY, toX, toY);
8978             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8979               case MT_NONE:
8980               case MT_CHECK:
8981                 break;
8982
8983               case MT_CHECKMATE:
8984               case MT_STAINMATE:
8985                 if (WhiteOnMove(currentMove)) {
8986                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8987                 } else {
8988                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8989                 }
8990                 break;
8991
8992               case MT_STALEMATE:
8993                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8994                 break;
8995             }
8996
8997             break;
8998
8999           case CMAIL_RESIGN:
9000             if (WhiteOnMove(currentMove)) {
9001                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9002             } else {
9003                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9004             }
9005             break;
9006
9007           case CMAIL_ACCEPT:
9008             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9009             break;
9010
9011           default:
9012             break;
9013         }
9014     }
9015
9016     return;
9017 }
9018
9019 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9020 int
9021 CmailLoadGame(f, gameNumber, title, useList)
9022      FILE *f;
9023      int gameNumber;
9024      char *title;
9025      int useList;
9026 {
9027     int retVal;
9028
9029     if (gameNumber > nCmailGames) {
9030         DisplayError(_("No more games in this message"), 0);
9031         return FALSE;
9032     }
9033     if (f == lastLoadGameFP) {
9034         int offset = gameNumber - lastLoadGameNumber;
9035         if (offset == 0) {
9036             cmailMsg[0] = NULLCHAR;
9037             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9038                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9039                 nCmailMovesRegistered--;
9040             }
9041             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9042             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9043                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9044             }
9045         } else {
9046             if (! RegisterMove()) return FALSE;
9047         }
9048     }
9049
9050     retVal = LoadGame(f, gameNumber, title, useList);
9051
9052     /* Make move registered during previous look at this game, if any */
9053     MakeRegisteredMove();
9054
9055     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9056         commentList[currentMove]
9057           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9058         DisplayComment(currentMove - 1, commentList[currentMove]);
9059     }
9060
9061     return retVal;
9062 }
9063
9064 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9065 int
9066 ReloadGame(offset)
9067      int offset;
9068 {
9069     int gameNumber = lastLoadGameNumber + offset;
9070     if (lastLoadGameFP == NULL) {
9071         DisplayError(_("No game has been loaded yet"), 0);
9072         return FALSE;
9073     }
9074     if (gameNumber <= 0) {
9075         DisplayError(_("Can't back up any further"), 0);
9076         return FALSE;
9077     }
9078     if (cmailMsgLoaded) {
9079         return CmailLoadGame(lastLoadGameFP, gameNumber,
9080                              lastLoadGameTitle, lastLoadGameUseList);
9081     } else {
9082         return LoadGame(lastLoadGameFP, gameNumber,
9083                         lastLoadGameTitle, lastLoadGameUseList);
9084     }
9085 }
9086
9087
9088
9089 /* Load the nth game from open file f */
9090 int
9091 LoadGame(f, gameNumber, title, useList)
9092      FILE *f;
9093      int gameNumber;
9094      char *title;
9095      int useList;
9096 {
9097     ChessMove cm;
9098     char buf[MSG_SIZ];
9099     int gn = gameNumber;
9100     ListGame *lg = NULL;
9101     int numPGNTags = 0;
9102     int err;
9103     GameMode oldGameMode;
9104     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9105
9106     if (appData.debugMode)
9107         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9108
9109     if (gameMode == Training )
9110         SetTrainingModeOff();
9111
9112     oldGameMode = gameMode;
9113     if (gameMode != BeginningOfGame) 
9114       {
9115         Reset(FALSE, TRUE);
9116       };
9117
9118     gameFileFP = f;
9119     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9120       {
9121         fclose(lastLoadGameFP);
9122       };
9123
9124     if (useList) 
9125       {
9126         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9127         
9128         if (lg) 
9129           {
9130             fseek(f, lg->offset, 0);
9131             GameListHighlight(gameNumber);
9132             gn = 1;
9133           }
9134         else 
9135           {
9136             DisplayError(_("Game number out of range"), 0);
9137             return FALSE;
9138           };
9139       } 
9140     else 
9141       {
9142         GameListDestroy();
9143         if (fseek(f, 0, 0) == -1) 
9144           {
9145             if (f == lastLoadGameFP ?
9146                 gameNumber == lastLoadGameNumber + 1 :
9147                 gameNumber == 1) 
9148               {
9149                 gn = 1;
9150               } 
9151             else 
9152               {
9153                 DisplayError(_("Can't seek on game file"), 0);
9154                 return FALSE;
9155               };
9156           };
9157       };
9158
9159     lastLoadGameFP      = f;
9160     lastLoadGameNumber  = gameNumber;
9161     strcpy(lastLoadGameTitle, title);
9162     lastLoadGameUseList = useList;
9163
9164     yynewfile(f);
9165
9166     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9167       {
9168         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9169                  lg->gameInfo.black);
9170         DisplayTitle(buf);
9171       } 
9172     else if (*title != NULLCHAR) 
9173       {
9174         if (gameNumber > 1) 
9175           {
9176             sprintf(buf, "%s %d", title, gameNumber);
9177             DisplayTitle(buf);
9178           } 
9179         else 
9180           {
9181             DisplayTitle(title);
9182           };
9183       };
9184
9185     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9186       {
9187         gameMode = PlayFromGameFile;
9188         ModeHighlight();
9189       };
9190
9191     currentMove = forwardMostMove = backwardMostMove = 0;
9192     CopyBoard(boards[0], initialPosition);
9193     StopClocks();
9194
9195     /*
9196      * Skip the first gn-1 games in the file.
9197      * Also skip over anything that precedes an identifiable
9198      * start of game marker, to avoid being confused by
9199      * garbage at the start of the file.  Currently
9200      * recognized start of game markers are the move number "1",
9201      * the pattern "gnuchess .* game", the pattern
9202      * "^[#;%] [^ ]* game file", and a PGN tag block.
9203      * A game that starts with one of the latter two patterns
9204      * will also have a move number 1, possibly
9205      * following a position diagram.
9206      * 5-4-02: Let's try being more lenient and allowing a game to
9207      * start with an unnumbered move.  Does that break anything?
9208      */
9209     cm = lastLoadGameStart = (ChessMove) 0;
9210     while (gn > 0) {
9211         yyboardindex = forwardMostMove;
9212         cm = (ChessMove) yylex();
9213         switch (cm) {
9214           case (ChessMove) 0:
9215             if (cmailMsgLoaded) {
9216                 nCmailGames = CMAIL_MAX_GAMES - gn;
9217             } else {
9218                 Reset(TRUE, TRUE);
9219                 DisplayError(_("Game not found in file"), 0);
9220             }
9221             return FALSE;
9222
9223           case GNUChessGame:
9224           case XBoardGame:
9225             gn--;
9226             lastLoadGameStart = cm;
9227             break;
9228
9229           case MoveNumberOne:
9230             switch (lastLoadGameStart) {
9231               case GNUChessGame:
9232               case XBoardGame:
9233               case PGNTag:
9234                 break;
9235               case MoveNumberOne:
9236               case (ChessMove) 0:
9237                 gn--;           /* count this game */
9238                 lastLoadGameStart = cm;
9239                 break;
9240               default:
9241                 /* impossible */
9242                 break;
9243             }
9244             break;
9245
9246           case PGNTag:
9247             switch (lastLoadGameStart) {
9248               case GNUChessGame:
9249               case PGNTag:
9250               case MoveNumberOne:
9251               case (ChessMove) 0:
9252                 gn--;           /* count this game */
9253                 lastLoadGameStart = cm;
9254                 break;
9255               case XBoardGame:
9256                 lastLoadGameStart = cm; /* game counted already */
9257                 break;
9258               default:
9259                 /* impossible */
9260                 break;
9261             }
9262             if (gn > 0) {
9263                 do {
9264                     yyboardindex = forwardMostMove;
9265                     cm = (ChessMove) yylex();
9266                 } while (cm == PGNTag || cm == Comment);
9267             }
9268             break;
9269
9270           case WhiteWins:
9271           case BlackWins:
9272           case GameIsDrawn:
9273             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9274                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9275                     != CMAIL_OLD_RESULT) {
9276                     nCmailResults ++ ;
9277                     cmailResult[  CMAIL_MAX_GAMES
9278                                 - gn - 1] = CMAIL_OLD_RESULT;
9279                 }
9280             }
9281             break;
9282
9283           case NormalMove:
9284             /* Only a NormalMove can be at the start of a game
9285              * without a position diagram. */
9286             if (lastLoadGameStart == (ChessMove) 0) {
9287               gn--;
9288               lastLoadGameStart = MoveNumberOne;
9289             }
9290             break;
9291
9292           default:
9293             break;
9294         }
9295     }
9296
9297     if (appData.debugMode)
9298       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9299
9300     if (cm == XBoardGame) {
9301         /* Skip any header junk before position diagram and/or move 1 */
9302         for (;;) {
9303             yyboardindex = forwardMostMove;
9304             cm = (ChessMove) yylex();
9305
9306             if (cm == (ChessMove) 0 ||
9307                 cm == GNUChessGame || cm == XBoardGame) {
9308                 /* Empty game; pretend end-of-file and handle later */
9309                 cm = (ChessMove) 0;
9310                 break;
9311             }
9312
9313             if (cm == MoveNumberOne || cm == PositionDiagram ||
9314                 cm == PGNTag || cm == Comment)
9315               break;
9316         }
9317     } else if (cm == GNUChessGame) {
9318         if (gameInfo.event != NULL) {
9319             free(gameInfo.event);
9320         }
9321         gameInfo.event = StrSave(yy_text);
9322     }
9323
9324     startedFromSetupPosition = FALSE;
9325     while (cm == PGNTag) {
9326         if (appData.debugMode)
9327           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9328         err = ParsePGNTag(yy_text, &gameInfo);
9329         if (!err) numPGNTags++;
9330
9331         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9332         if(gameInfo.variant != oldVariant) {
9333             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9334             InitPosition(TRUE);
9335             oldVariant = gameInfo.variant;
9336             if (appData.debugMode)
9337               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9338         }
9339
9340
9341         if (gameInfo.fen != NULL) {
9342           Board initial_position;
9343           startedFromSetupPosition = TRUE;
9344           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9345             Reset(TRUE, TRUE);
9346             DisplayError(_("Bad FEN position in file"), 0);
9347             return FALSE;
9348           }
9349           CopyBoard(boards[0], initial_position);
9350           if (blackPlaysFirst) {
9351             currentMove = forwardMostMove = backwardMostMove = 1;
9352             CopyBoard(boards[1], initial_position);
9353             strcpy(moveList[0], "");
9354             strcpy(parseList[0], "");
9355             timeRemaining[0][1] = whiteTimeRemaining;
9356             timeRemaining[1][1] = blackTimeRemaining;
9357             if (commentList[0] != NULL) {
9358               commentList[1] = commentList[0];
9359               commentList[0] = NULL;
9360             }
9361           } else {
9362             currentMove = forwardMostMove = backwardMostMove = 0;
9363           }
9364           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9365           {   int i;
9366               initialRulePlies = FENrulePlies;
9367               for( i=0; i< nrCastlingRights; i++ )
9368                   initialRights[i] = initial_position[CASTLING][i];
9369           }
9370           yyboardindex = forwardMostMove;
9371           free(gameInfo.fen);
9372           gameInfo.fen = NULL;
9373         }
9374
9375         yyboardindex = forwardMostMove;
9376         cm = (ChessMove) yylex();
9377
9378         /* Handle comments interspersed among the tags */
9379         while (cm == Comment) {
9380             char *p;
9381             if (appData.debugMode)
9382               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9383             p = yy_text;
9384             AppendComment(currentMove, p, FALSE);
9385             yyboardindex = forwardMostMove;
9386             cm = (ChessMove) yylex();
9387         }
9388     }
9389
9390     /* don't rely on existence of Event tag since if game was
9391      * pasted from clipboard the Event tag may not exist
9392      */
9393     if (numPGNTags > 0){
9394         char *tags;
9395         if (gameInfo.variant == VariantNormal) {
9396           gameInfo.variant = StringToVariant(gameInfo.event);
9397         }
9398         if (!matchMode) {
9399           if( appData.autoDisplayTags ) {
9400             tags = PGNTags(&gameInfo);
9401             TagsPopUp(tags, CmailMsg());
9402             free(tags);
9403           }
9404         }
9405     } else {
9406         /* Make something up, but don't display it now */
9407         SetGameInfo();
9408         TagsPopDown();
9409     }
9410
9411     if (cm == PositionDiagram) {
9412         int i, j;
9413         char *p;
9414         Board initial_position;
9415
9416         if (appData.debugMode)
9417           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9418
9419         if (!startedFromSetupPosition) {
9420             p = yy_text;
9421             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9422               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9423                 switch (*p) {
9424                   case '[':
9425                   case '-':
9426                   case ' ':
9427                   case '\t':
9428                   case '\n':
9429                   case '\r':
9430                     break;
9431                   default:
9432                     initial_position[i][j++] = CharToPiece(*p);
9433                     break;
9434                 }
9435             while (*p == ' ' || *p == '\t' ||
9436                    *p == '\n' || *p == '\r') p++;
9437
9438             if (strncmp(p, "black", strlen("black"))==0)
9439               blackPlaysFirst = TRUE;
9440             else
9441               blackPlaysFirst = FALSE;
9442             startedFromSetupPosition = TRUE;
9443
9444             CopyBoard(boards[0], initial_position);
9445             if (blackPlaysFirst) {
9446                 currentMove = forwardMostMove = backwardMostMove = 1;
9447                 CopyBoard(boards[1], initial_position);
9448                 strcpy(moveList[0], "");
9449                 strcpy(parseList[0], "");
9450                 timeRemaining[0][1] = whiteTimeRemaining;
9451                 timeRemaining[1][1] = blackTimeRemaining;
9452                 if (commentList[0] != NULL) {
9453                     commentList[1] = commentList[0];
9454                     commentList[0] = NULL;
9455                 }
9456             } else {
9457                 currentMove = forwardMostMove = backwardMostMove = 0;
9458             }
9459         }
9460         yyboardindex = forwardMostMove;
9461         cm = (ChessMove) yylex();
9462     }
9463
9464     if (first.pr == NoProc) {
9465         StartChessProgram(&first);
9466     }
9467     InitChessProgram(&first, FALSE);
9468     SendToProgram("force\n", &first);
9469     if (startedFromSetupPosition) {
9470         SendBoard(&first, forwardMostMove);
9471     if (appData.debugMode) {
9472         fprintf(debugFP, "Load Game\n");
9473     }
9474         DisplayBothClocks();
9475     }
9476
9477     /* [HGM] server: flag to write setup moves in broadcast file as one */
9478     loadFlag = appData.suppressLoadMoves;
9479
9480     while (cm == Comment) {
9481         char *p;
9482         if (appData.debugMode)
9483           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9484         p = yy_text;
9485         AppendComment(currentMove, p, FALSE);
9486         yyboardindex = forwardMostMove;
9487         cm = (ChessMove) yylex();
9488     }
9489
9490     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9491         cm == WhiteWins || cm == BlackWins ||
9492         cm == GameIsDrawn || cm == GameUnfinished) {
9493         DisplayMessage("", _("No moves in game"));
9494         if (cmailMsgLoaded) {
9495             if (appData.debugMode)
9496               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9497             ClearHighlights();
9498             flipView = FALSE;
9499         }
9500         DrawPosition(FALSE, boards[currentMove]);
9501         DisplayBothClocks();
9502         gameMode = EditGame;
9503         ModeHighlight();
9504         gameFileFP = NULL;
9505         cmailOldMove = 0;
9506         return TRUE;
9507     }
9508
9509     // [HGM] PV info: routine tests if comment empty
9510     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9511         DisplayComment(currentMove - 1, commentList[currentMove]);
9512     }
9513     if (!matchMode && appData.timeDelay != 0)
9514       DrawPosition(FALSE, boards[currentMove]);
9515
9516     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9517       programStats.ok_to_send = 1;
9518     }
9519
9520     /* if the first token after the PGN tags is a move
9521      * and not move number 1, retrieve it from the parser
9522      */
9523     if (cm != MoveNumberOne)
9524         LoadGameOneMove(cm);
9525
9526     /* load the remaining moves from the file */
9527     while (LoadGameOneMove((ChessMove)0)) {
9528       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9529       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9530     }
9531
9532     /* rewind to the start of the game */
9533     currentMove = backwardMostMove;
9534
9535     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9536
9537     if (oldGameMode == AnalyzeFile ||
9538         oldGameMode == AnalyzeMode) {
9539       AnalyzeFileEvent();
9540     }
9541
9542     if (matchMode || appData.timeDelay == 0) {
9543       ToEndEvent();
9544       gameMode = EditGame;
9545       ModeHighlight();
9546     } else if (appData.timeDelay > 0) {
9547       AutoPlayGameLoop();
9548     }
9549
9550     if (appData.debugMode)
9551         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9552
9553     loadFlag = 0; /* [HGM] true game starts */
9554     return TRUE;
9555 }
9556
9557 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9558 int
9559 ReloadPosition(offset)
9560      int offset;
9561 {
9562     int positionNumber = lastLoadPositionNumber + offset;
9563     if (lastLoadPositionFP == NULL) {
9564         DisplayError(_("No position has been loaded yet"), 0);
9565         return FALSE;
9566     }
9567     if (positionNumber <= 0) {
9568         DisplayError(_("Can't back up any further"), 0);
9569         return FALSE;
9570     }
9571     return LoadPosition(lastLoadPositionFP, positionNumber,
9572                         lastLoadPositionTitle);
9573 }
9574
9575 /* Load the nth position from the given file */
9576 int
9577 LoadPositionFromFile(filename, n, title)
9578      char *filename;
9579      int n;
9580      char *title;
9581 {
9582     FILE *f;
9583     char buf[MSG_SIZ];
9584
9585     if (strcmp(filename, "-") == 0) {
9586         return LoadPosition(stdin, n, "stdin");
9587     } else {
9588         f = fopen(filename, "rb");
9589         if (f == NULL) {
9590             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9591             DisplayError(buf, errno);
9592             return FALSE;
9593         } else {
9594             return LoadPosition(f, n, title);
9595         }
9596     }
9597 }
9598
9599 /* Load the nth position from the given open file, and close it */
9600 int
9601 LoadPosition(f, positionNumber, title)
9602      FILE *f;
9603      int positionNumber;
9604      char *title;
9605 {
9606     char *p, line[MSG_SIZ];
9607     Board initial_position;
9608     int i, j, fenMode, pn;
9609
9610     if (gameMode == Training )
9611         SetTrainingModeOff();
9612
9613     if (gameMode != BeginningOfGame) {
9614         Reset(FALSE, TRUE);
9615     }
9616     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9617         fclose(lastLoadPositionFP);
9618     }
9619     if (positionNumber == 0) positionNumber = 1;
9620     lastLoadPositionFP = f;
9621     lastLoadPositionNumber = positionNumber;
9622     strcpy(lastLoadPositionTitle, title);
9623     if (first.pr == NoProc) {
9624       StartChessProgram(&first);
9625       InitChessProgram(&first, FALSE);
9626     }
9627     pn = positionNumber;
9628     if (positionNumber < 0) {
9629         /* Negative position number means to seek to that byte offset */
9630         if (fseek(f, -positionNumber, 0) == -1) {
9631             DisplayError(_("Can't seek on position file"), 0);
9632             return FALSE;
9633         };
9634         pn = 1;
9635     } else {
9636         if (fseek(f, 0, 0) == -1) {
9637             if (f == lastLoadPositionFP ?
9638                 positionNumber == lastLoadPositionNumber + 1 :
9639                 positionNumber == 1) {
9640                 pn = 1;
9641             } else {
9642                 DisplayError(_("Can't seek on position file"), 0);
9643                 return FALSE;
9644             }
9645         }
9646     }
9647     /* See if this file is FEN or old-style xboard */
9648     if (fgets(line, MSG_SIZ, f) == NULL) {
9649         DisplayError(_("Position not found in file"), 0);
9650         return FALSE;
9651     }
9652     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9653     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9654
9655     if (pn >= 2) {
9656         if (fenMode || line[0] == '#') pn--;
9657         while (pn > 0) {
9658             /* skip positions before number pn */
9659             if (fgets(line, MSG_SIZ, f) == NULL) {
9660                 Reset(TRUE, TRUE);
9661                 DisplayError(_("Position not found in file"), 0);
9662                 return FALSE;
9663             }
9664             if (fenMode || line[0] == '#') pn--;
9665         }
9666     }
9667
9668     if (fenMode) {
9669         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9670             DisplayError(_("Bad FEN position in file"), 0);
9671             return FALSE;
9672         }
9673     } else {
9674         (void) fgets(line, MSG_SIZ, f);
9675         (void) fgets(line, MSG_SIZ, f);
9676
9677         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9678             (void) fgets(line, MSG_SIZ, f);
9679             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9680                 if (*p == ' ')
9681                   continue;
9682                 initial_position[i][j++] = CharToPiece(*p);
9683             }
9684         }
9685
9686         blackPlaysFirst = FALSE;
9687         if (!feof(f)) {
9688             (void) fgets(line, MSG_SIZ, f);
9689             if (strncmp(line, "black", strlen("black"))==0)
9690               blackPlaysFirst = TRUE;
9691         }
9692     }
9693     startedFromSetupPosition = TRUE;
9694
9695     SendToProgram("force\n", &first);
9696     CopyBoard(boards[0], initial_position);
9697     if (blackPlaysFirst) {
9698         currentMove = forwardMostMove = backwardMostMove = 1;
9699         strcpy(moveList[0], "");
9700         strcpy(parseList[0], "");
9701         CopyBoard(boards[1], initial_position);
9702         DisplayMessage("", _("Black to play"));
9703     } else {
9704         currentMove = forwardMostMove = backwardMostMove = 0;
9705         DisplayMessage("", _("White to play"));
9706     }
9707     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9708     SendBoard(&first, forwardMostMove);
9709     if (appData.debugMode) {
9710 int i, j;
9711   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9712   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9713         fprintf(debugFP, "Load Position\n");
9714     }
9715
9716     if (positionNumber > 1) {
9717         sprintf(line, "%s %d", title, positionNumber);
9718         DisplayTitle(line);
9719     } else {
9720         DisplayTitle(title);
9721     }
9722     gameMode = EditGame;
9723     ModeHighlight();
9724     ResetClocks();
9725     timeRemaining[0][1] = whiteTimeRemaining;
9726     timeRemaining[1][1] = blackTimeRemaining;
9727     DrawPosition(FALSE, boards[currentMove]);
9728
9729     return TRUE;
9730 }
9731
9732
9733 void
9734 CopyPlayerNameIntoFileName(dest, src)
9735      char **dest, *src;
9736 {
9737     while (*src != NULLCHAR && *src != ',') {
9738         if (*src == ' ') {
9739             *(*dest)++ = '_';
9740             src++;
9741         } else {
9742             *(*dest)++ = *src++;
9743         }
9744     }
9745 }
9746
9747 char *DefaultFileName(ext)
9748      char *ext;
9749 {
9750     static char def[MSG_SIZ];
9751     char *p;
9752
9753     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9754         p = def;
9755         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9756         *p++ = '-';
9757         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9758         *p++ = '.';
9759         strcpy(p, ext);
9760     } else {
9761         def[0] = NULLCHAR;
9762     }
9763     return def;
9764 }
9765
9766 /* Save the current game to the given file */
9767 int
9768 SaveGameToFile(filename, append)
9769      char *filename;
9770      int append;
9771 {
9772     FILE *f;
9773     char buf[MSG_SIZ];
9774
9775     if (strcmp(filename, "-") == 0) {
9776         return SaveGame(stdout, 0, NULL);
9777     } else {
9778         f = fopen(filename, append ? "a" : "w");
9779         if (f == NULL) {
9780             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9781             DisplayError(buf, errno);
9782             return FALSE;
9783         } else {
9784             return SaveGame(f, 0, NULL);
9785         }
9786     }
9787 }
9788
9789 char *
9790 SavePart(str)
9791      char *str;
9792 {
9793     static char buf[MSG_SIZ];
9794     char *p;
9795
9796     p = strchr(str, ' ');
9797     if (p == NULL) return str;
9798     strncpy(buf, str, p - str);
9799     buf[p - str] = NULLCHAR;
9800     return buf;
9801 }
9802
9803 #define PGN_MAX_LINE 75
9804
9805 #define PGN_SIDE_WHITE  0
9806 #define PGN_SIDE_BLACK  1
9807
9808 /* [AS] */
9809 static int FindFirstMoveOutOfBook( int side )
9810 {
9811     int result = -1;
9812
9813     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9814         int index = backwardMostMove;
9815         int has_book_hit = 0;
9816
9817         if( (index % 2) != side ) {
9818             index++;
9819         }
9820
9821         while( index < forwardMostMove ) {
9822             /* Check to see if engine is in book */
9823             int depth = pvInfoList[index].depth;
9824             int score = pvInfoList[index].score;
9825             int in_book = 0;
9826
9827             if( depth <= 2 ) {
9828                 in_book = 1;
9829             }
9830             else if( score == 0 && depth == 63 ) {
9831                 in_book = 1; /* Zappa */
9832             }
9833             else if( score == 2 && depth == 99 ) {
9834                 in_book = 1; /* Abrok */
9835             }
9836
9837             has_book_hit += in_book;
9838
9839             if( ! in_book ) {
9840                 result = index;
9841
9842                 break;
9843             }
9844
9845             index += 2;
9846         }
9847     }
9848
9849     return result;
9850 }
9851
9852 /* [AS] */
9853 void GetOutOfBookInfo( char * buf )
9854 {
9855     int oob[2];
9856     int i;
9857     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9858
9859     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9860     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9861
9862     *buf = '\0';
9863
9864     if( oob[0] >= 0 || oob[1] >= 0 ) {
9865         for( i=0; i<2; i++ ) {
9866             int idx = oob[i];
9867
9868             if( idx >= 0 ) {
9869                 if( i > 0 && oob[0] >= 0 ) {
9870                     strcat( buf, "   " );
9871                 }
9872
9873                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9874                 sprintf( buf+strlen(buf), "%s%.2f",
9875                     pvInfoList[idx].score >= 0 ? "+" : "",
9876                     pvInfoList[idx].score / 100.0 );
9877             }
9878         }
9879     }
9880 }
9881
9882 /* Save game in PGN style and close the file */
9883 int
9884 SaveGamePGN(f)
9885      FILE *f;
9886 {
9887     int i, offset, linelen, newblock;
9888     time_t tm;
9889 //    char *movetext;
9890     char numtext[32];
9891     int movelen, numlen, blank;
9892     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9893
9894     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9895
9896     tm = time((time_t *) NULL);
9897
9898     PrintPGNTags(f, &gameInfo);
9899
9900     if (backwardMostMove > 0 || startedFromSetupPosition) {
9901         char *fen = PositionToFEN(backwardMostMove, NULL);
9902         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9903         fprintf(f, "\n{--------------\n");
9904         PrintPosition(f, backwardMostMove);
9905         fprintf(f, "--------------}\n");
9906         free(fen);
9907     }
9908     else {
9909         /* [AS] Out of book annotation */
9910         if( appData.saveOutOfBookInfo ) {
9911             char buf[64];
9912
9913             GetOutOfBookInfo( buf );
9914
9915             if( buf[0] != '\0' ) {
9916                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9917             }
9918         }
9919
9920         fprintf(f, "\n");
9921     }
9922
9923     i = backwardMostMove;
9924     linelen = 0;
9925     newblock = TRUE;
9926
9927     while (i < forwardMostMove) {
9928         /* Print comments preceding this move */
9929         if (commentList[i] != NULL) {
9930             if (linelen > 0) fprintf(f, "\n");
9931             fprintf(f, "%s", commentList[i]);
9932             linelen = 0;
9933             newblock = TRUE;
9934         }
9935
9936         /* Format move number */
9937         if ((i % 2) == 0) {
9938             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9939         } else {
9940             if (newblock) {
9941                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9942             } else {
9943                 numtext[0] = NULLCHAR;
9944             }
9945         }
9946         numlen = strlen(numtext);
9947         newblock = FALSE;
9948
9949         /* Print move number */
9950         blank = linelen > 0 && numlen > 0;
9951         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9952             fprintf(f, "\n");
9953             linelen = 0;
9954             blank = 0;
9955         }
9956         if (blank) {
9957             fprintf(f, " ");
9958             linelen++;
9959         }
9960         fprintf(f, "%s", numtext);
9961         linelen += numlen;
9962
9963         /* Get move */
9964         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9965         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9966
9967         /* Print move */
9968         blank = linelen > 0 && movelen > 0;
9969         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9970             fprintf(f, "\n");
9971             linelen = 0;
9972             blank = 0;
9973         }
9974         if (blank) {
9975             fprintf(f, " ");
9976             linelen++;
9977         }
9978         fprintf(f, "%s", move_buffer);
9979         linelen += movelen;
9980
9981         /* [AS] Add PV info if present */
9982         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9983             /* [HGM] add time */
9984             char buf[MSG_SIZ]; int seconds;
9985
9986             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9987
9988             if( seconds <= 0) buf[0] = 0; else
9989             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9990                 seconds = (seconds + 4)/10; // round to full seconds
9991                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9992                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9993             }
9994
9995             sprintf( move_buffer, "{%s%.2f/%d%s}",
9996                 pvInfoList[i].score >= 0 ? "+" : "",
9997                 pvInfoList[i].score / 100.0,
9998                 pvInfoList[i].depth,
9999                 buf );
10000
10001             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10002
10003             /* Print score/depth */
10004             blank = linelen > 0 && movelen > 0;
10005             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10006                 fprintf(f, "\n");
10007                 linelen = 0;
10008                 blank = 0;
10009             }
10010             if (blank) {
10011                 fprintf(f, " ");
10012                 linelen++;
10013             }
10014             fprintf(f, "%s", move_buffer);
10015             linelen += movelen;
10016         }
10017
10018         i++;
10019     }
10020
10021     /* Start a new line */
10022     if (linelen > 0) fprintf(f, "\n");
10023
10024     /* Print comments after last move */
10025     if (commentList[i] != NULL) {
10026         fprintf(f, "%s\n", commentList[i]);
10027     }
10028
10029     /* Print result */
10030     if (gameInfo.resultDetails != NULL &&
10031         gameInfo.resultDetails[0] != NULLCHAR) {
10032         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10033                 PGNResult(gameInfo.result));
10034     } else {
10035         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10036     }
10037
10038     fclose(f);
10039     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10040     return TRUE;
10041 }
10042
10043 /* Save game in old style and close the file */
10044 int
10045 SaveGameOldStyle(f)
10046      FILE *f;
10047 {
10048     int i, offset;
10049     time_t tm;
10050
10051     tm = time((time_t *) NULL);
10052
10053     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10054     PrintOpponents(f);
10055
10056     if (backwardMostMove > 0 || startedFromSetupPosition) {
10057         fprintf(f, "\n[--------------\n");
10058         PrintPosition(f, backwardMostMove);
10059         fprintf(f, "--------------]\n");
10060     } else {
10061         fprintf(f, "\n");
10062     }
10063
10064     i = backwardMostMove;
10065     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10066
10067     while (i < forwardMostMove) {
10068         if (commentList[i] != NULL) {
10069             fprintf(f, "[%s]\n", commentList[i]);
10070         }
10071
10072         if ((i % 2) == 1) {
10073             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10074             i++;
10075         } else {
10076             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10077             i++;
10078             if (commentList[i] != NULL) {
10079                 fprintf(f, "\n");
10080                 continue;
10081             }
10082             if (i >= forwardMostMove) {
10083                 fprintf(f, "\n");
10084                 break;
10085             }
10086             fprintf(f, "%s\n", parseList[i]);
10087             i++;
10088         }
10089     }
10090
10091     if (commentList[i] != NULL) {
10092         fprintf(f, "[%s]\n", commentList[i]);
10093     }
10094
10095     /* This isn't really the old style, but it's close enough */
10096     if (gameInfo.resultDetails != NULL &&
10097         gameInfo.resultDetails[0] != NULLCHAR) {
10098         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10099                 gameInfo.resultDetails);
10100     } else {
10101         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10102     }
10103
10104     fclose(f);
10105     return TRUE;
10106 }
10107
10108 /* Save the current game to open file f and close the file */
10109 int
10110 SaveGame(f, dummy, dummy2)
10111      FILE *f;
10112      int dummy;
10113      char *dummy2;
10114 {
10115     if (gameMode == EditPosition) EditPositionDone(TRUE);
10116     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10117     if (appData.oldSaveStyle)
10118       return SaveGameOldStyle(f);
10119     else
10120       return SaveGamePGN(f);
10121 }
10122
10123 /* Save the current position to the given file */
10124 int
10125 SavePositionToFile(filename)
10126      char *filename;
10127 {
10128     FILE *f;
10129     char buf[MSG_SIZ];
10130
10131     if (strcmp(filename, "-") == 0) {
10132         return SavePosition(stdout, 0, NULL);
10133     } else {
10134         f = fopen(filename, "a");
10135         if (f == NULL) {
10136             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10137             DisplayError(buf, errno);
10138             return FALSE;
10139         } else {
10140             SavePosition(f, 0, NULL);
10141             return TRUE;
10142         }
10143     }
10144 }
10145
10146 /* Save the current position to the given open file and close the file */
10147 int
10148 SavePosition(f, dummy, dummy2)
10149      FILE *f;
10150      int dummy;
10151      char *dummy2;
10152 {
10153     time_t tm;
10154     char *fen;
10155     if (gameMode == EditPosition) EditPositionDone(TRUE);
10156     if (appData.oldSaveStyle) {
10157         tm = time((time_t *) NULL);
10158
10159         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10160         PrintOpponents(f);
10161         fprintf(f, "[--------------\n");
10162         PrintPosition(f, currentMove);
10163         fprintf(f, "--------------]\n");
10164     } else {
10165         fen = PositionToFEN(currentMove, NULL);
10166         fprintf(f, "%s\n", fen);
10167         free(fen);
10168     }
10169     fclose(f);
10170     return TRUE;
10171 }
10172
10173 void
10174 ReloadCmailMsgEvent(unregister)
10175      int unregister;
10176 {
10177 #if !WIN32
10178     static char *inFilename = NULL;
10179     static char *outFilename;
10180     int i;
10181     struct stat inbuf, outbuf;
10182     int status;
10183
10184     /* Any registered moves are unregistered if unregister is set, */
10185     /* i.e. invoked by the signal handler */
10186     if (unregister) {
10187         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10188             cmailMoveRegistered[i] = FALSE;
10189             if (cmailCommentList[i] != NULL) {
10190                 free(cmailCommentList[i]);
10191                 cmailCommentList[i] = NULL;
10192             }
10193         }
10194         nCmailMovesRegistered = 0;
10195     }
10196
10197     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10198         cmailResult[i] = CMAIL_NOT_RESULT;
10199     }
10200     nCmailResults = 0;
10201
10202     if (inFilename == NULL) {
10203         /* Because the filenames are static they only get malloced once  */
10204         /* and they never get freed                                      */
10205         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10206         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10207
10208         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10209         sprintf(outFilename, "%s.out", appData.cmailGameName);
10210     }
10211
10212     status = stat(outFilename, &outbuf);
10213     if (status < 0) {
10214         cmailMailedMove = FALSE;
10215     } else {
10216         status = stat(inFilename, &inbuf);
10217         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10218     }
10219
10220     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10221        counts the games, notes how each one terminated, etc.
10222
10223        It would be nice to remove this kludge and instead gather all
10224        the information while building the game list.  (And to keep it
10225        in the game list nodes instead of having a bunch of fixed-size
10226        parallel arrays.)  Note this will require getting each game's
10227        termination from the PGN tags, as the game list builder does
10228        not process the game moves.  --mann
10229        */
10230     cmailMsgLoaded = TRUE;
10231     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10232
10233     /* Load first game in the file or popup game menu */
10234     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10235
10236 #endif /* !WIN32 */
10237     return;
10238 }
10239
10240 int
10241 RegisterMove()
10242 {
10243     FILE *f;
10244     char string[MSG_SIZ];
10245
10246     if (   cmailMailedMove
10247         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10248         return TRUE;            /* Allow free viewing  */
10249     }
10250
10251     /* Unregister move to ensure that we don't leave RegisterMove        */
10252     /* with the move registered when the conditions for registering no   */
10253     /* longer hold                                                       */
10254     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10255         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10256         nCmailMovesRegistered --;
10257
10258         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10259           {
10260               free(cmailCommentList[lastLoadGameNumber - 1]);
10261               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10262           }
10263     }
10264
10265     if (cmailOldMove == -1) {
10266         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10267         return FALSE;
10268     }
10269
10270     if (currentMove > cmailOldMove + 1) {
10271         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10272         return FALSE;
10273     }
10274
10275     if (currentMove < cmailOldMove) {
10276         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10277         return FALSE;
10278     }
10279
10280     if (forwardMostMove > currentMove) {
10281         /* Silently truncate extra moves */
10282         TruncateGame();
10283     }
10284
10285     if (   (currentMove == cmailOldMove + 1)
10286         || (   (currentMove == cmailOldMove)
10287             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10288                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10289         if (gameInfo.result != GameUnfinished) {
10290             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10291         }
10292
10293         if (commentList[currentMove] != NULL) {
10294             cmailCommentList[lastLoadGameNumber - 1]
10295               = StrSave(commentList[currentMove]);
10296         }
10297         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10298
10299         if (appData.debugMode)
10300           fprintf(debugFP, "Saving %s for game %d\n",
10301                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10302
10303         sprintf(string,
10304                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10305
10306         f = fopen(string, "w");
10307         if (appData.oldSaveStyle) {
10308             SaveGameOldStyle(f); /* also closes the file */
10309
10310             sprintf(string, "%s.pos.out", appData.cmailGameName);
10311             f = fopen(string, "w");
10312             SavePosition(f, 0, NULL); /* also closes the file */
10313         } else {
10314             fprintf(f, "{--------------\n");
10315             PrintPosition(f, currentMove);
10316             fprintf(f, "--------------}\n\n");
10317
10318             SaveGame(f, 0, NULL); /* also closes the file*/
10319         }
10320
10321         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10322         nCmailMovesRegistered ++;
10323     } else if (nCmailGames == 1) {
10324         DisplayError(_("You have not made a move yet"), 0);
10325         return FALSE;
10326     }
10327
10328     return TRUE;
10329 }
10330
10331 void
10332 MailMoveEvent()
10333 {
10334 #if !WIN32
10335     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10336     FILE *commandOutput;
10337     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10338     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10339     int nBuffers;
10340     int i;
10341     int archived;
10342     char *arcDir;
10343
10344     if (! cmailMsgLoaded) {
10345         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10346         return;
10347     }
10348
10349     if (nCmailGames == nCmailResults) {
10350         DisplayError(_("No unfinished games"), 0);
10351         return;
10352     }
10353
10354 #if CMAIL_PROHIBIT_REMAIL
10355     if (cmailMailedMove) {
10356         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);
10357         DisplayError(msg, 0);
10358         return;
10359     }
10360 #endif
10361
10362     if (! (cmailMailedMove || RegisterMove())) return;
10363
10364     if (   cmailMailedMove
10365         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10366         sprintf(string, partCommandString,
10367                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10368         commandOutput = popen(string, "r");
10369
10370         if (commandOutput == NULL) {
10371             DisplayError(_("Failed to invoke cmail"), 0);
10372         } else {
10373             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10374                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10375             }
10376             if (nBuffers > 1) {
10377                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10378                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10379                 nBytes = MSG_SIZ - 1;
10380             } else {
10381                 (void) memcpy(msg, buffer, nBytes);
10382             }
10383             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10384
10385             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10386                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10387
10388                 archived = TRUE;
10389                 for (i = 0; i < nCmailGames; i ++) {
10390                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10391                         archived = FALSE;
10392                     }
10393                 }
10394                 if (   archived
10395                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10396                         != NULL)) {
10397                     sprintf(buffer, "%s/%s.%s.archive",
10398                             arcDir,
10399                             appData.cmailGameName,
10400                             gameInfo.date);
10401                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10402                     cmailMsgLoaded = FALSE;
10403                 }
10404             }
10405
10406             DisplayInformation(msg);
10407             pclose(commandOutput);
10408         }
10409     } else {
10410         if ((*cmailMsg) != '\0') {
10411             DisplayInformation(cmailMsg);
10412         }
10413     }
10414
10415     return;
10416 #endif /* !WIN32 */
10417 }
10418
10419 char *
10420 CmailMsg()
10421 {
10422 #if WIN32
10423     return NULL;
10424 #else
10425     int  prependComma = 0;
10426     char number[5];
10427     char string[MSG_SIZ];       /* Space for game-list */
10428     int  i;
10429
10430     if (!cmailMsgLoaded) return "";
10431
10432     if (cmailMailedMove) {
10433         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10434     } else {
10435         /* Create a list of games left */
10436         sprintf(string, "[");
10437         for (i = 0; i < nCmailGames; i ++) {
10438             if (! (   cmailMoveRegistered[i]
10439                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10440                 if (prependComma) {
10441                     sprintf(number, ",%d", i + 1);
10442                 } else {
10443                     sprintf(number, "%d", i + 1);
10444                     prependComma = 1;
10445                 }
10446
10447                 strcat(string, number);
10448             }
10449         }
10450         strcat(string, "]");
10451
10452         if (nCmailMovesRegistered + nCmailResults == 0) {
10453             switch (nCmailGames) {
10454               case 1:
10455                 sprintf(cmailMsg,
10456                         _("Still need to make move for game\n"));
10457                 break;
10458
10459               case 2:
10460                 sprintf(cmailMsg,
10461                         _("Still need to make moves for both games\n"));
10462                 break;
10463
10464               default:
10465                 sprintf(cmailMsg,
10466                         _("Still need to make moves for all %d games\n"),
10467                         nCmailGames);
10468                 break;
10469             }
10470         } else {
10471             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10472               case 1:
10473                 sprintf(cmailMsg,
10474                         _("Still need to make a move for game %s\n"),
10475                         string);
10476                 break;
10477
10478               case 0:
10479                 if (nCmailResults == nCmailGames) {
10480                     sprintf(cmailMsg, _("No unfinished games\n"));
10481                 } else {
10482                     sprintf(cmailMsg, _("Ready to send mail\n"));
10483                 }
10484                 break;
10485
10486               default:
10487                 sprintf(cmailMsg,
10488                         _("Still need to make moves for games %s\n"),
10489                         string);
10490             }
10491         }
10492     }
10493     return cmailMsg;
10494 #endif /* WIN32 */
10495 }
10496
10497 void
10498 ResetGameEvent()
10499 {
10500     if (gameMode == Training)
10501       SetTrainingModeOff();
10502
10503     Reset(TRUE, TRUE);
10504     cmailMsgLoaded = FALSE;
10505     if (appData.icsActive) {
10506       SendToICS(ics_prefix);
10507       SendToICS("refresh\n");
10508     }
10509 }
10510
10511 void
10512 ExitEvent(status)
10513      int status;
10514 {
10515     exiting++;
10516     if (exiting > 2) {
10517       /* Give up on clean exit */
10518       exit(status);
10519     }
10520     if (exiting > 1) {
10521       /* Keep trying for clean exit */
10522       return;
10523     }
10524
10525     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10526
10527     if (telnetISR != NULL) {
10528       RemoveInputSource(telnetISR);
10529     }
10530     if (icsPR != NoProc) {
10531       DestroyChildProcess(icsPR, TRUE);
10532     }
10533
10534     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10535     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10536
10537     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10538     /* make sure this other one finishes before killing it!                  */
10539     if(endingGame) { int count = 0;
10540         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10541         while(endingGame && count++ < 10) DoSleep(1);
10542         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10543     }
10544
10545     /* Kill off chess programs */
10546     if (first.pr != NoProc) {
10547         ExitAnalyzeMode();
10548
10549         DoSleep( appData.delayBeforeQuit );
10550         SendToProgram("quit\n", &first);
10551         DoSleep( appData.delayAfterQuit );
10552         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10553     }
10554     if (second.pr != NoProc) {
10555         DoSleep( appData.delayBeforeQuit );
10556         SendToProgram("quit\n", &second);
10557         DoSleep( appData.delayAfterQuit );
10558         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10559     }
10560     if (first.isr != NULL) {
10561         RemoveInputSource(first.isr);
10562     }
10563     if (second.isr != NULL) {
10564         RemoveInputSource(second.isr);
10565     }
10566
10567     ShutDownFrontEnd();
10568     exit(status);
10569 }
10570
10571 void
10572 PauseEvent()
10573 {
10574     if (appData.debugMode)
10575         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10576     if (pausing) {
10577         pausing = FALSE;
10578         ModeHighlight();
10579         if (gameMode == MachinePlaysWhite ||
10580             gameMode == MachinePlaysBlack) {
10581             StartClocks();
10582         } else {
10583             DisplayBothClocks();
10584         }
10585         if (gameMode == PlayFromGameFile) {
10586             if (appData.timeDelay >= 0)
10587                 AutoPlayGameLoop();
10588         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10589             Reset(FALSE, TRUE);
10590             SendToICS(ics_prefix);
10591             SendToICS("refresh\n");
10592         } else if (currentMove < forwardMostMove) {
10593             ForwardInner(forwardMostMove);
10594         }
10595         pauseExamInvalid = FALSE;
10596     } else {
10597         switch (gameMode) {
10598           default:
10599             return;
10600           case IcsExamining:
10601             pauseExamForwardMostMove = forwardMostMove;
10602             pauseExamInvalid = FALSE;
10603             /* fall through */
10604           case IcsObserving:
10605           case IcsPlayingWhite:
10606           case IcsPlayingBlack:
10607             pausing = TRUE;
10608             ModeHighlight();
10609             return;
10610           case PlayFromGameFile:
10611             (void) StopLoadGameTimer();
10612             pausing = TRUE;
10613             ModeHighlight();
10614             break;
10615           case BeginningOfGame:
10616             if (appData.icsActive) return;
10617             /* else fall through */
10618           case MachinePlaysWhite:
10619           case MachinePlaysBlack:
10620           case TwoMachinesPlay:
10621             if (forwardMostMove == 0)
10622               return;           /* don't pause if no one has moved */
10623             if ((gameMode == MachinePlaysWhite &&
10624                  !WhiteOnMove(forwardMostMove)) ||
10625                 (gameMode == MachinePlaysBlack &&
10626                  WhiteOnMove(forwardMostMove))) {
10627                 StopClocks();
10628             }
10629             pausing = TRUE;
10630             ModeHighlight();
10631             break;
10632         }
10633     }
10634 }
10635
10636 void
10637 EditCommentEvent()
10638 {
10639     char title[MSG_SIZ];
10640
10641     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10642         strcpy(title, _("Edit comment"));
10643     } else {
10644         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10645                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10646                 parseList[currentMove - 1]);
10647     }
10648
10649     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10650 }
10651
10652
10653 void
10654 EditTagsEvent()
10655 {
10656     char *tags = PGNTags(&gameInfo);
10657     EditTagsPopUp(tags);
10658     free(tags);
10659 }
10660
10661 void
10662 AnalyzeModeEvent()
10663 {
10664     if (appData.noChessProgram || gameMode == AnalyzeMode)
10665       return;
10666
10667     if (gameMode != AnalyzeFile) {
10668         if (!appData.icsEngineAnalyze) {
10669                EditGameEvent();
10670                if (gameMode != EditGame) return;
10671         }
10672         ResurrectChessProgram();
10673         SendToProgram("analyze\n", &first);
10674         first.analyzing = TRUE;
10675         /*first.maybeThinking = TRUE;*/
10676         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10677         EngineOutputPopUp();
10678     }
10679     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10680     pausing = FALSE;
10681     ModeHighlight();
10682     SetGameInfo();
10683
10684     StartAnalysisClock();
10685     GetTimeMark(&lastNodeCountTime);
10686     lastNodeCount = 0;
10687 }
10688
10689 void
10690 AnalyzeFileEvent()
10691 {
10692     if (appData.noChessProgram || gameMode == AnalyzeFile)
10693       return;
10694
10695     if (gameMode != AnalyzeMode) {
10696         EditGameEvent();
10697         if (gameMode != EditGame) return;
10698         ResurrectChessProgram();
10699         SendToProgram("analyze\n", &first);
10700         first.analyzing = TRUE;
10701         /*first.maybeThinking = TRUE;*/
10702         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10703         EngineOutputPopUp();
10704     }
10705     gameMode = AnalyzeFile;
10706     pausing = FALSE;
10707     ModeHighlight();
10708     SetGameInfo();
10709
10710     StartAnalysisClock();
10711     GetTimeMark(&lastNodeCountTime);
10712     lastNodeCount = 0;
10713 }
10714
10715 void
10716 MachineWhiteEvent()
10717 {
10718     char buf[MSG_SIZ];
10719     char *bookHit = NULL;
10720
10721     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10722       return;
10723
10724
10725     if (gameMode == PlayFromGameFile ||
10726         gameMode == TwoMachinesPlay  ||
10727         gameMode == Training         ||
10728         gameMode == AnalyzeMode      ||
10729         gameMode == EndOfGame)
10730         EditGameEvent();
10731
10732     if (gameMode == EditPosition) 
10733         EditPositionDone(TRUE);
10734
10735     if (!WhiteOnMove(currentMove)) {
10736         DisplayError(_("It is not White's turn"), 0);
10737         return;
10738     }
10739
10740     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10741       ExitAnalyzeMode();
10742
10743     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10744         gameMode == AnalyzeFile)
10745         TruncateGame();
10746
10747     ResurrectChessProgram();    /* in case it isn't running */
10748     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10749         gameMode = MachinePlaysWhite;
10750         ResetClocks();
10751     } else
10752     gameMode = MachinePlaysWhite;
10753     pausing = FALSE;
10754     ModeHighlight();
10755     SetGameInfo();
10756     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10757     DisplayTitle(buf);
10758     if (first.sendName) {
10759       sprintf(buf, "name %s\n", gameInfo.black);
10760       SendToProgram(buf, &first);
10761     }
10762     if (first.sendTime) {
10763       if (first.useColors) {
10764         SendToProgram("black\n", &first); /*gnu kludge*/
10765       }
10766       SendTimeRemaining(&first, TRUE);
10767     }
10768     if (first.useColors) {
10769       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10770     }
10771     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10772     SetMachineThinkingEnables();
10773     first.maybeThinking = TRUE;
10774     StartClocks();
10775     firstMove = FALSE;
10776
10777     if (appData.autoFlipView && !flipView) {
10778       flipView = !flipView;
10779       DrawPosition(FALSE, NULL);
10780       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10781     }
10782
10783     if(bookHit) { // [HGM] book: simulate book reply
10784         static char bookMove[MSG_SIZ]; // a bit generous?
10785
10786         programStats.nodes = programStats.depth = programStats.time =
10787         programStats.score = programStats.got_only_move = 0;
10788         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10789
10790         strcpy(bookMove, "move ");
10791         strcat(bookMove, bookHit);
10792         HandleMachineMove(bookMove, &first);
10793     }
10794 }
10795
10796 void
10797 MachineBlackEvent()
10798 {
10799     char buf[MSG_SIZ];
10800    char *bookHit = NULL;
10801
10802     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10803         return;
10804
10805
10806     if (gameMode == PlayFromGameFile ||
10807         gameMode == TwoMachinesPlay  ||
10808         gameMode == Training         ||
10809         gameMode == AnalyzeMode      ||
10810         gameMode == EndOfGame)
10811         EditGameEvent();
10812
10813     if (gameMode == EditPosition) 
10814         EditPositionDone(TRUE);
10815
10816     if (WhiteOnMove(currentMove)) {
10817         DisplayError(_("It is not Black's turn"), 0);
10818         return;
10819     }
10820
10821     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10822       ExitAnalyzeMode();
10823
10824     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10825         gameMode == AnalyzeFile)
10826         TruncateGame();
10827
10828     ResurrectChessProgram();    /* in case it isn't running */
10829     gameMode = MachinePlaysBlack;
10830     pausing = FALSE;
10831     ModeHighlight();
10832     SetGameInfo();
10833     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10834     DisplayTitle(buf);
10835     if (first.sendName) {
10836       sprintf(buf, "name %s\n", gameInfo.white);
10837       SendToProgram(buf, &first);
10838     }
10839     if (first.sendTime) {
10840       if (first.useColors) {
10841         SendToProgram("white\n", &first); /*gnu kludge*/
10842       }
10843       SendTimeRemaining(&first, FALSE);
10844     }
10845     if (first.useColors) {
10846       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10847     }
10848     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10849     SetMachineThinkingEnables();
10850     first.maybeThinking = TRUE;
10851     StartClocks();
10852
10853     if (appData.autoFlipView && flipView) {
10854       flipView = !flipView;
10855       DrawPosition(FALSE, NULL);
10856       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10857     }
10858     if(bookHit) { // [HGM] book: simulate book reply
10859         static char bookMove[MSG_SIZ]; // a bit generous?
10860
10861         programStats.nodes = programStats.depth = programStats.time =
10862         programStats.score = programStats.got_only_move = 0;
10863         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10864
10865         strcpy(bookMove, "move ");
10866         strcat(bookMove, bookHit);
10867         HandleMachineMove(bookMove, &first);
10868     }
10869 }
10870
10871
10872 void
10873 DisplayTwoMachinesTitle()
10874 {
10875     char buf[MSG_SIZ];
10876     if (appData.matchGames > 0) {
10877         if (first.twoMachinesColor[0] == 'w') {
10878             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10879                     gameInfo.white, gameInfo.black,
10880                     first.matchWins, second.matchWins,
10881                     matchGame - 1 - (first.matchWins + second.matchWins));
10882         } else {
10883             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10884                     gameInfo.white, gameInfo.black,
10885                     second.matchWins, first.matchWins,
10886                     matchGame - 1 - (first.matchWins + second.matchWins));
10887         }
10888     } else {
10889         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10890     }
10891     DisplayTitle(buf);
10892 }
10893
10894 void
10895 TwoMachinesEvent P((void))
10896 {
10897     int i;
10898     char buf[MSG_SIZ];
10899     ChessProgramState *onmove;
10900     char *bookHit = NULL;
10901
10902     if (appData.noChessProgram) return;
10903
10904     switch (gameMode) {
10905       case TwoMachinesPlay:
10906         return;
10907       case MachinePlaysWhite:
10908       case MachinePlaysBlack:
10909         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10910             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10911             return;
10912         }
10913         /* fall through */
10914       case BeginningOfGame:
10915       case PlayFromGameFile:
10916       case EndOfGame:
10917         EditGameEvent();
10918         if (gameMode != EditGame) return;
10919         break;
10920       case EditPosition:
10921         EditPositionDone(TRUE);
10922         break;
10923       case AnalyzeMode:
10924       case AnalyzeFile:
10925         ExitAnalyzeMode();
10926         break;
10927       case EditGame:
10928       default:
10929         break;
10930     }
10931
10932 //    forwardMostMove = currentMove;
10933     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10934     ResurrectChessProgram();    /* in case first program isn't running */
10935
10936     if (second.pr == NULL) {
10937         StartChessProgram(&second);
10938         if (second.protocolVersion == 1) {
10939           TwoMachinesEventIfReady();
10940         } else {
10941           /* kludge: allow timeout for initial "feature" command */
10942           FreezeUI();
10943           DisplayMessage("", _("Starting second chess program"));
10944           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10945         }
10946         return;
10947     }
10948     DisplayMessage("", "");
10949     InitChessProgram(&second, FALSE);
10950     SendToProgram("force\n", &second);
10951     if (startedFromSetupPosition) {
10952         SendBoard(&second, backwardMostMove);
10953     if (appData.debugMode) {
10954         fprintf(debugFP, "Two Machines\n");
10955     }
10956     }
10957     for (i = backwardMostMove; i < forwardMostMove; i++) {
10958         SendMoveToProgram(i, &second);
10959     }
10960
10961     gameMode = TwoMachinesPlay;
10962     pausing = FALSE;
10963     ModeHighlight();
10964     SetGameInfo();
10965     DisplayTwoMachinesTitle();
10966     firstMove = TRUE;
10967     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10968         onmove = &first;
10969     } else {
10970         onmove = &second;
10971     }
10972
10973     SendToProgram(first.computerString, &first);
10974     if (first.sendName) {
10975       sprintf(buf, "name %s\n", second.tidy);
10976       SendToProgram(buf, &first);
10977     }
10978     SendToProgram(second.computerString, &second);
10979     if (second.sendName) {
10980       sprintf(buf, "name %s\n", first.tidy);
10981       SendToProgram(buf, &second);
10982     }
10983
10984     ResetClocks();
10985     if (!first.sendTime || !second.sendTime) {
10986         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10987         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10988     }
10989     if (onmove->sendTime) {
10990       if (onmove->useColors) {
10991         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10992       }
10993       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10994     }
10995     if (onmove->useColors) {
10996       SendToProgram(onmove->twoMachinesColor, onmove);
10997     }
10998     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10999 //    SendToProgram("go\n", onmove);
11000     onmove->maybeThinking = TRUE;
11001     SetMachineThinkingEnables();
11002
11003     StartClocks();
11004
11005     if(bookHit) { // [HGM] book: simulate book reply
11006         static char bookMove[MSG_SIZ]; // a bit generous?
11007
11008         programStats.nodes = programStats.depth = programStats.time =
11009         programStats.score = programStats.got_only_move = 0;
11010         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11011
11012         strcpy(bookMove, "move ");
11013         strcat(bookMove, bookHit);
11014         savedMessage = bookMove; // args for deferred call
11015         savedState = onmove;
11016         ScheduleDelayedEvent(DeferredBookMove, 1);
11017     }
11018 }
11019
11020 void
11021 TrainingEvent()
11022 {
11023     if (gameMode == Training) {
11024       SetTrainingModeOff();
11025       gameMode = PlayFromGameFile;
11026       DisplayMessage("", _("Training mode off"));
11027     } else {
11028       gameMode = Training;
11029       animateTraining = appData.animate;
11030
11031       /* make sure we are not already at the end of the game */
11032       if (currentMove < forwardMostMove) {
11033         SetTrainingModeOn();
11034         DisplayMessage("", _("Training mode on"));
11035       } else {
11036         gameMode = PlayFromGameFile;
11037         DisplayError(_("Already at end of game"), 0);
11038       }
11039     }
11040     ModeHighlight();
11041 }
11042
11043 void
11044 IcsClientEvent()
11045 {
11046     if (!appData.icsActive) return;
11047     switch (gameMode) {
11048       case IcsPlayingWhite:
11049       case IcsPlayingBlack:
11050       case IcsObserving:
11051       case IcsIdle:
11052       case BeginningOfGame:
11053       case IcsExamining:
11054         return;
11055
11056       case EditGame:
11057         break;
11058
11059       case EditPosition:
11060         EditPositionDone(TRUE);
11061         break;
11062
11063       case AnalyzeMode:
11064       case AnalyzeFile:
11065         ExitAnalyzeMode();
11066         break;
11067
11068       default:
11069         EditGameEvent();
11070         break;
11071     }
11072
11073     gameMode = IcsIdle;
11074     ModeHighlight();
11075     return;
11076 }
11077
11078
11079 void
11080 EditGameEvent()
11081 {
11082     int i;
11083
11084     switch (gameMode) {
11085       case Training:
11086         SetTrainingModeOff();
11087         break;
11088       case MachinePlaysWhite:
11089       case MachinePlaysBlack:
11090       case BeginningOfGame:
11091         SendToProgram("force\n", &first);
11092         SetUserThinkingEnables();
11093         break;
11094       case PlayFromGameFile:
11095         (void) StopLoadGameTimer();
11096         if (gameFileFP != NULL) {
11097             gameFileFP = NULL;
11098         }
11099         break;
11100       case EditPosition:
11101         EditPositionDone(TRUE);
11102         break;
11103       case AnalyzeMode:
11104       case AnalyzeFile:
11105         ExitAnalyzeMode();
11106         SendToProgram("force\n", &first);
11107         break;
11108       case TwoMachinesPlay:
11109         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11110         ResurrectChessProgram();
11111         SetUserThinkingEnables();
11112         break;
11113       case EndOfGame:
11114         ResurrectChessProgram();
11115         break;
11116       case IcsPlayingBlack:
11117       case IcsPlayingWhite:
11118         DisplayError(_("Warning: You are still playing a game"), 0);
11119         break;
11120       case IcsObserving:
11121         DisplayError(_("Warning: You are still observing a game"), 0);
11122         break;
11123       case IcsExamining:
11124         DisplayError(_("Warning: You are still examining a game"), 0);
11125         break;
11126       case IcsIdle:
11127         break;
11128       case EditGame:
11129       default:
11130         return;
11131     }
11132
11133     pausing = FALSE;
11134     StopClocks();
11135     first.offeredDraw = second.offeredDraw = 0;
11136
11137     if (gameMode == PlayFromGameFile) {
11138         whiteTimeRemaining = timeRemaining[0][currentMove];
11139         blackTimeRemaining = timeRemaining[1][currentMove];
11140         DisplayTitle("");
11141     }
11142
11143     if (gameMode == MachinePlaysWhite ||
11144         gameMode == MachinePlaysBlack ||
11145         gameMode == TwoMachinesPlay ||
11146         gameMode == EndOfGame) {
11147         i = forwardMostMove;
11148         while (i > currentMove) {
11149             SendToProgram("undo\n", &first);
11150             i--;
11151         }
11152         whiteTimeRemaining = timeRemaining[0][currentMove];
11153         blackTimeRemaining = timeRemaining[1][currentMove];
11154         DisplayBothClocks();
11155         if (whiteFlag || blackFlag) {
11156             whiteFlag = blackFlag = 0;
11157         }
11158         DisplayTitle("");
11159     }
11160
11161     gameMode = EditGame;
11162     ModeHighlight();
11163     SetGameInfo();
11164 }
11165
11166
11167 void
11168 EditPositionEvent()
11169 {
11170     if (gameMode == EditPosition) {
11171         EditGameEvent();
11172         return;
11173     }
11174
11175     EditGameEvent();
11176     if (gameMode != EditGame) return;
11177
11178     gameMode = EditPosition;
11179     ModeHighlight();
11180     SetGameInfo();
11181     if (currentMove > 0)
11182       CopyBoard(boards[0], boards[currentMove]);
11183
11184     blackPlaysFirst = !WhiteOnMove(currentMove);
11185     ResetClocks();
11186     currentMove = forwardMostMove = backwardMostMove = 0;
11187     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11188     DisplayMove(-1);
11189 }
11190
11191 void
11192 ExitAnalyzeMode()
11193 {
11194     /* [DM] icsEngineAnalyze - possible call from other functions */
11195     if (appData.icsEngineAnalyze) {
11196         appData.icsEngineAnalyze = FALSE;
11197
11198         DisplayMessage("",_("Close ICS engine analyze..."));
11199     }
11200     if (first.analysisSupport && first.analyzing) {
11201       SendToProgram("exit\n", &first);
11202       first.analyzing = FALSE;
11203     }
11204     thinkOutput[0] = NULLCHAR;
11205 }
11206
11207 void
11208 EditPositionDone(Boolean fakeRights)
11209 {
11210     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11211
11212     startedFromSetupPosition = TRUE;
11213     InitChessProgram(&first, FALSE);
11214     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11215       boards[0][EP_STATUS] = EP_NONE;
11216       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11217     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11218         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11219         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11220       } else boards[0][CASTLING][2] = NoRights;
11221     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11222         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11223         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11224       } else boards[0][CASTLING][5] = NoRights;
11225     }
11226     SendToProgram("force\n", &first);
11227     if (blackPlaysFirst) {
11228         strcpy(moveList[0], "");
11229         strcpy(parseList[0], "");
11230         currentMove = forwardMostMove = backwardMostMove = 1;
11231         CopyBoard(boards[1], boards[0]);
11232     } else {
11233         currentMove = forwardMostMove = backwardMostMove = 0;
11234     }
11235     SendBoard(&first, forwardMostMove);
11236     if (appData.debugMode) {
11237         fprintf(debugFP, "EditPosDone\n");
11238     }
11239     DisplayTitle("");
11240     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11241     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11242     gameMode = EditGame;
11243     ModeHighlight();
11244     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11245     ClearHighlights(); /* [AS] */
11246 }
11247
11248 /* Pause for `ms' milliseconds */
11249 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11250 void
11251 TimeDelay(ms)
11252      long ms;
11253 {
11254     TimeMark m1, m2;
11255
11256     GetTimeMark(&m1);
11257     do {
11258         GetTimeMark(&m2);
11259     } while (SubtractTimeMarks(&m2, &m1) < ms);
11260 }
11261
11262 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11263 void
11264 SendMultiLineToICS(buf)
11265      char *buf;
11266 {
11267     char temp[MSG_SIZ+1], *p;
11268     int len;
11269
11270     len = strlen(buf);
11271     if (len > MSG_SIZ)
11272       len = MSG_SIZ;
11273
11274     strncpy(temp, buf, len);
11275     temp[len] = 0;
11276
11277     p = temp;
11278     while (*p) {
11279         if (*p == '\n' || *p == '\r')
11280           *p = ' ';
11281         ++p;
11282     }
11283
11284     strcat(temp, "\n");
11285     SendToICS(temp);
11286     SendToPlayer(temp, strlen(temp));
11287 }
11288
11289 void
11290 SetWhiteToPlayEvent()
11291 {
11292     if (gameMode == EditPosition) {
11293         blackPlaysFirst = FALSE;
11294         DisplayBothClocks();    /* works because currentMove is 0 */
11295     } else if (gameMode == IcsExamining) {
11296         SendToICS(ics_prefix);
11297         SendToICS("tomove white\n");
11298     }
11299 }
11300
11301 void
11302 SetBlackToPlayEvent()
11303 {
11304     if (gameMode == EditPosition) {
11305         blackPlaysFirst = TRUE;
11306         currentMove = 1;        /* kludge */
11307         DisplayBothClocks();
11308         currentMove = 0;
11309     } else if (gameMode == IcsExamining) {
11310         SendToICS(ics_prefix);
11311         SendToICS("tomove black\n");
11312     }
11313 }
11314
11315 void
11316 EditPositionMenuEvent(selection, x, y)
11317      ChessSquare selection;
11318      int x, y;
11319 {
11320     char buf[MSG_SIZ];
11321     ChessSquare piece = boards[0][y][x];
11322
11323     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11324
11325     switch (selection) {
11326       case ClearBoard:
11327         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11328             SendToICS(ics_prefix);
11329             SendToICS("bsetup clear\n");
11330         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11331             SendToICS(ics_prefix);
11332             SendToICS("clearboard\n");
11333         } else {
11334             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11335                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11336                 for (y = 0; y < BOARD_HEIGHT; y++) {
11337                     if (gameMode == IcsExamining) {
11338                         if (boards[currentMove][y][x] != EmptySquare) {
11339                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11340                                     AAA + x, ONE + y);
11341                             SendToICS(buf);
11342                         }
11343                     } else {
11344                         boards[0][y][x] = p;
11345                     }
11346                 }
11347             }
11348         }
11349         if (gameMode == EditPosition) {
11350             DrawPosition(FALSE, boards[0]);
11351         }
11352         break;
11353
11354       case WhitePlay:
11355         SetWhiteToPlayEvent();
11356         break;
11357
11358       case BlackPlay:
11359         SetBlackToPlayEvent();
11360         break;
11361
11362       case EmptySquare:
11363         if (gameMode == IcsExamining) {
11364             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11365             SendToICS(buf);
11366         } else {
11367             boards[0][y][x] = EmptySquare;
11368             DrawPosition(FALSE, boards[0]);
11369         }
11370         break;
11371
11372       case PromotePiece:
11373         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11374            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11375             selection = (ChessSquare) (PROMOTED piece);
11376         } else if(piece == EmptySquare) selection = WhiteSilver;
11377         else selection = (ChessSquare)((int)piece - 1);
11378         goto defaultlabel;
11379
11380       case DemotePiece:
11381         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11382            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11383             selection = (ChessSquare) (DEMOTED piece);
11384         } else if(piece == EmptySquare) selection = BlackSilver;
11385         else selection = (ChessSquare)((int)piece + 1);
11386         goto defaultlabel;
11387
11388       case WhiteQueen:
11389       case BlackQueen:
11390         if(gameInfo.variant == VariantShatranj ||
11391            gameInfo.variant == VariantXiangqi  ||
11392            gameInfo.variant == VariantCourier    )
11393             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11394         goto defaultlabel;
11395
11396       case WhiteKing:
11397       case BlackKing:
11398         if(gameInfo.variant == VariantXiangqi)
11399             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11400         if(gameInfo.variant == VariantKnightmate)
11401             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11402       default:
11403         defaultlabel:
11404         if (gameMode == IcsExamining) {
11405             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11406                     PieceToChar(selection), AAA + x, ONE + y);
11407             SendToICS(buf);
11408         } else {
11409             boards[0][y][x] = selection;
11410             DrawPosition(FALSE, boards[0]);
11411         }
11412         break;
11413     }
11414 }
11415
11416
11417 void
11418 DropMenuEvent(selection, x, y)
11419      ChessSquare selection;
11420      int x, y;
11421 {
11422     ChessMove moveType;
11423
11424     switch (gameMode) {
11425       case IcsPlayingWhite:
11426       case MachinePlaysBlack:
11427         if (!WhiteOnMove(currentMove)) {
11428             DisplayMoveError(_("It is Black's turn"));
11429             return;
11430         }
11431         moveType = WhiteDrop;
11432         break;
11433       case IcsPlayingBlack:
11434       case MachinePlaysWhite:
11435         if (WhiteOnMove(currentMove)) {
11436             DisplayMoveError(_("It is White's turn"));
11437             return;
11438         }
11439         moveType = BlackDrop;
11440         break;
11441       case EditGame:
11442         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11443         break;
11444       default:
11445         return;
11446     }
11447
11448     if (moveType == BlackDrop && selection < BlackPawn) {
11449       selection = (ChessSquare) ((int) selection
11450                                  + (int) BlackPawn - (int) WhitePawn);
11451     }
11452     if (boards[currentMove][y][x] != EmptySquare) {
11453         DisplayMoveError(_("That square is occupied"));
11454         return;
11455     }
11456
11457     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11458 }
11459
11460 void
11461 AcceptEvent()
11462 {
11463     /* Accept a pending offer of any kind from opponent */
11464
11465     if (appData.icsActive) {
11466         SendToICS(ics_prefix);
11467         SendToICS("accept\n");
11468     } else if (cmailMsgLoaded) {
11469         if (currentMove == cmailOldMove &&
11470             commentList[cmailOldMove] != NULL &&
11471             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11472                    "Black offers a draw" : "White offers a draw")) {
11473             TruncateGame();
11474             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11475             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11476         } else {
11477             DisplayError(_("There is no pending offer on this move"), 0);
11478             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11479         }
11480     } else {
11481         /* Not used for offers from chess program */
11482     }
11483 }
11484
11485 void
11486 DeclineEvent()
11487 {
11488     /* Decline a pending offer of any kind from opponent */
11489
11490     if (appData.icsActive) {
11491         SendToICS(ics_prefix);
11492         SendToICS("decline\n");
11493     } else if (cmailMsgLoaded) {
11494         if (currentMove == cmailOldMove &&
11495             commentList[cmailOldMove] != NULL &&
11496             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11497                    "Black offers a draw" : "White offers a draw")) {
11498 #ifdef NOTDEF
11499             AppendComment(cmailOldMove, "Draw declined", TRUE);
11500             DisplayComment(cmailOldMove - 1, "Draw declined");
11501 #endif /*NOTDEF*/
11502         } else {
11503             DisplayError(_("There is no pending offer on this move"), 0);
11504         }
11505     } else {
11506         /* Not used for offers from chess program */
11507     }
11508 }
11509
11510 void
11511 RematchEvent()
11512 {
11513     /* Issue ICS rematch command */
11514     if (appData.icsActive) {
11515         SendToICS(ics_prefix);
11516         SendToICS("rematch\n");
11517     }
11518 }
11519
11520 void
11521 CallFlagEvent()
11522 {
11523     /* Call your opponent's flag (claim a win on time) */
11524     if (appData.icsActive) {
11525         SendToICS(ics_prefix);
11526         SendToICS("flag\n");
11527     } else {
11528         switch (gameMode) {
11529           default:
11530             return;
11531           case MachinePlaysWhite:
11532             if (whiteFlag) {
11533                 if (blackFlag)
11534                   GameEnds(GameIsDrawn, "Both players ran out of time",
11535                            GE_PLAYER);
11536                 else
11537                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11538             } else {
11539                 DisplayError(_("Your opponent is not out of time"), 0);
11540             }
11541             break;
11542           case MachinePlaysBlack:
11543             if (blackFlag) {
11544                 if (whiteFlag)
11545                   GameEnds(GameIsDrawn, "Both players ran out of time",
11546                            GE_PLAYER);
11547                 else
11548                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11549             } else {
11550                 DisplayError(_("Your opponent is not out of time"), 0);
11551             }
11552             break;
11553         }
11554     }
11555 }
11556
11557 void
11558 DrawEvent()
11559 {
11560     /* Offer draw or accept pending draw offer from opponent */
11561
11562     if (appData.icsActive) {
11563         /* Note: tournament rules require draw offers to be
11564            made after you make your move but before you punch
11565            your clock.  Currently ICS doesn't let you do that;
11566            instead, you immediately punch your clock after making
11567            a move, but you can offer a draw at any time. */
11568
11569         SendToICS(ics_prefix);
11570         SendToICS("draw\n");
11571     } else if (cmailMsgLoaded) {
11572         if (currentMove == cmailOldMove &&
11573             commentList[cmailOldMove] != NULL &&
11574             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11575                    "Black offers a draw" : "White offers a draw")) {
11576             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11577             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11578         } else if (currentMove == cmailOldMove + 1) {
11579             char *offer = WhiteOnMove(cmailOldMove) ?
11580               "White offers a draw" : "Black offers a draw";
11581             AppendComment(currentMove, offer, TRUE);
11582             DisplayComment(currentMove - 1, offer);
11583             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11584         } else {
11585             DisplayError(_("You must make your move before offering a draw"), 0);
11586             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11587         }
11588     } else if (first.offeredDraw) {
11589         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11590     } else {
11591         if (first.sendDrawOffers) {
11592             SendToProgram("draw\n", &first);
11593             userOfferedDraw = TRUE;
11594         }
11595     }
11596 }
11597
11598 void
11599 AdjournEvent()
11600 {
11601     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11602
11603     if (appData.icsActive) {
11604         SendToICS(ics_prefix);
11605         SendToICS("adjourn\n");
11606     } else {
11607         /* Currently GNU Chess doesn't offer or accept Adjourns */
11608     }
11609 }
11610
11611
11612 void
11613 AbortEvent()
11614 {
11615     /* Offer Abort or accept pending Abort offer from opponent */
11616
11617     if (appData.icsActive) {
11618         SendToICS(ics_prefix);
11619         SendToICS("abort\n");
11620     } else {
11621         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11622     }
11623 }
11624
11625 void
11626 ResignEvent()
11627 {
11628     /* Resign.  You can do this even if it's not your turn. */
11629
11630     if (appData.icsActive) {
11631         SendToICS(ics_prefix);
11632         SendToICS("resign\n");
11633     } else {
11634         switch (gameMode) {
11635           case MachinePlaysWhite:
11636             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11637             break;
11638           case MachinePlaysBlack:
11639             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11640             break;
11641           case EditGame:
11642             if (cmailMsgLoaded) {
11643                 TruncateGame();
11644                 if (WhiteOnMove(cmailOldMove)) {
11645                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11646                 } else {
11647                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11648                 }
11649                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11650             }
11651             break;
11652           default:
11653             break;
11654         }
11655     }
11656 }
11657
11658
11659 void
11660 StopObservingEvent()
11661 {
11662     /* Stop observing current games */
11663     SendToICS(ics_prefix);
11664     SendToICS("unobserve\n");
11665 }
11666
11667 void
11668 StopExaminingEvent()
11669 {
11670     /* Stop observing current game */
11671     SendToICS(ics_prefix);
11672     SendToICS("unexamine\n");
11673 }
11674
11675 void
11676 ForwardInner(target)
11677      int target;
11678 {
11679     int limit;
11680
11681     if (appData.debugMode)
11682         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11683                 target, currentMove, forwardMostMove);
11684
11685     if (gameMode == EditPosition)
11686       return;
11687
11688     if (gameMode == PlayFromGameFile && !pausing)
11689       PauseEvent();
11690
11691     if (gameMode == IcsExamining && pausing)
11692       limit = pauseExamForwardMostMove;
11693     else
11694       limit = forwardMostMove;
11695
11696     if (target > limit) target = limit;
11697
11698     if (target > 0 && moveList[target - 1][0]) {
11699         int fromX, fromY, toX, toY;
11700         toX = moveList[target - 1][2] - AAA;
11701         toY = moveList[target - 1][3] - ONE;
11702         if (moveList[target - 1][1] == '@') {
11703             if (appData.highlightLastMove) {
11704                 SetHighlights(-1, -1, toX, toY);
11705             }
11706         } else {
11707             fromX = moveList[target - 1][0] - AAA;
11708             fromY = moveList[target - 1][1] - ONE;
11709             if (target == currentMove + 1) {
11710                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11711             }
11712             if (appData.highlightLastMove) {
11713                 SetHighlights(fromX, fromY, toX, toY);
11714             }
11715         }
11716     }
11717     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11718         gameMode == Training || gameMode == PlayFromGameFile ||
11719         gameMode == AnalyzeFile) {
11720         while (currentMove < target) {
11721             SendMoveToProgram(currentMove++, &first);
11722         }
11723     } else {
11724         currentMove = target;
11725     }
11726
11727     if (gameMode == EditGame || gameMode == EndOfGame) {
11728         whiteTimeRemaining = timeRemaining[0][currentMove];
11729         blackTimeRemaining = timeRemaining[1][currentMove];
11730     }
11731     DisplayBothClocks();
11732     DisplayMove(currentMove - 1);
11733     DrawPosition(FALSE, boards[currentMove]);
11734     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11735     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11736         DisplayComment(currentMove - 1, commentList[currentMove]);
11737     }
11738 }
11739
11740
11741 void
11742 ForwardEvent()
11743 {
11744     if (gameMode == IcsExamining && !pausing) {
11745         SendToICS(ics_prefix);
11746         SendToICS("forward\n");
11747     } else {
11748         ForwardInner(currentMove + 1);
11749     }
11750 }
11751
11752 void
11753 ToEndEvent()
11754 {
11755     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11756         /* to optimze, we temporarily turn off analysis mode while we feed
11757          * the remaining moves to the engine. Otherwise we get analysis output
11758          * after each move.
11759          */
11760         if (first.analysisSupport) {
11761           SendToProgram("exit\nforce\n", &first);
11762           first.analyzing = FALSE;
11763         }
11764     }
11765
11766     if (gameMode == IcsExamining && !pausing) {
11767         SendToICS(ics_prefix);
11768         SendToICS("forward 999999\n");
11769     } else {
11770         ForwardInner(forwardMostMove);
11771     }
11772
11773     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11774         /* we have fed all the moves, so reactivate analysis mode */
11775         SendToProgram("analyze\n", &first);
11776         first.analyzing = TRUE;
11777         /*first.maybeThinking = TRUE;*/
11778         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11779     }
11780 }
11781
11782 void
11783 BackwardInner(target)
11784      int target;
11785 {
11786     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11787
11788     if (appData.debugMode)
11789         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11790                 target, currentMove, forwardMostMove);
11791
11792     if (gameMode == EditPosition) return;
11793     if (currentMove <= backwardMostMove) {
11794         ClearHighlights();
11795         DrawPosition(full_redraw, boards[currentMove]);
11796         return;
11797     }
11798     if (gameMode == PlayFromGameFile && !pausing)
11799       PauseEvent();
11800
11801     if (moveList[target][0]) {
11802         int fromX, fromY, toX, toY;
11803         toX = moveList[target][2] - AAA;
11804         toY = moveList[target][3] - ONE;
11805         if (moveList[target][1] == '@') {
11806             if (appData.highlightLastMove) {
11807                 SetHighlights(-1, -1, toX, toY);
11808             }
11809         } else {
11810             fromX = moveList[target][0] - AAA;
11811             fromY = moveList[target][1] - ONE;
11812             if (target == currentMove - 1) {
11813                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11814             }
11815             if (appData.highlightLastMove) {
11816                 SetHighlights(fromX, fromY, toX, toY);
11817             }
11818         }
11819     }
11820     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11821         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11822         while (currentMove > target) {
11823             SendToProgram("undo\n", &first);
11824             currentMove--;
11825         }
11826     } else {
11827         currentMove = target;
11828     }
11829
11830     if (gameMode == EditGame || gameMode == EndOfGame) {
11831         whiteTimeRemaining = timeRemaining[0][currentMove];
11832         blackTimeRemaining = timeRemaining[1][currentMove];
11833     }
11834     DisplayBothClocks();
11835     DisplayMove(currentMove - 1);
11836     DrawPosition(full_redraw, boards[currentMove]);
11837     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11838     // [HGM] PV info: routine tests if comment empty
11839     DisplayComment(currentMove - 1, commentList[currentMove]);
11840 }
11841
11842 void
11843 BackwardEvent()
11844 {
11845     if (gameMode == IcsExamining && !pausing) {
11846         SendToICS(ics_prefix);
11847         SendToICS("backward\n");
11848     } else {
11849         BackwardInner(currentMove - 1);
11850     }
11851 }
11852
11853 void
11854 ToStartEvent()
11855 {
11856     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11857         /* to optimize, we temporarily turn off analysis mode while we undo
11858          * all the moves. Otherwise we get analysis output after each undo.
11859          */
11860         if (first.analysisSupport) {
11861           SendToProgram("exit\nforce\n", &first);
11862           first.analyzing = FALSE;
11863         }
11864     }
11865
11866     if (gameMode == IcsExamining && !pausing) {
11867         SendToICS(ics_prefix);
11868         SendToICS("backward 999999\n");
11869     } else {
11870         BackwardInner(backwardMostMove);
11871     }
11872
11873     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11874         /* we have fed all the moves, so reactivate analysis mode */
11875         SendToProgram("analyze\n", &first);
11876         first.analyzing = TRUE;
11877         /*first.maybeThinking = TRUE;*/
11878         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11879     }
11880 }
11881
11882 void
11883 ToNrEvent(int to)
11884 {
11885   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11886   if (to >= forwardMostMove) to = forwardMostMove;
11887   if (to <= backwardMostMove) to = backwardMostMove;
11888   if (to < currentMove) {
11889     BackwardInner(to);
11890   } else {
11891     ForwardInner(to);
11892   }
11893 }
11894
11895 void
11896 RevertEvent()
11897 {
11898     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11899         return;
11900     }
11901     if (gameMode != IcsExamining) {
11902         DisplayError(_("You are not examining a game"), 0);
11903         return;
11904     }
11905     if (pausing) {
11906         DisplayError(_("You can't revert while pausing"), 0);
11907         return;
11908     }
11909     SendToICS(ics_prefix);
11910     SendToICS("revert\n");
11911 }
11912
11913 void
11914 RetractMoveEvent()
11915 {
11916     switch (gameMode) {
11917       case MachinePlaysWhite:
11918       case MachinePlaysBlack:
11919         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11920             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11921             return;
11922         }
11923         if (forwardMostMove < 2) return;
11924         currentMove = forwardMostMove = forwardMostMove - 2;
11925         whiteTimeRemaining = timeRemaining[0][currentMove];
11926         blackTimeRemaining = timeRemaining[1][currentMove];
11927         DisplayBothClocks();
11928         DisplayMove(currentMove - 1);
11929         ClearHighlights();/*!! could figure this out*/
11930         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11931         SendToProgram("remove\n", &first);
11932         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11933         break;
11934
11935       case BeginningOfGame:
11936       default:
11937         break;
11938
11939       case IcsPlayingWhite:
11940       case IcsPlayingBlack:
11941         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11942             SendToICS(ics_prefix);
11943             SendToICS("takeback 2\n");
11944         } else {
11945             SendToICS(ics_prefix);
11946             SendToICS("takeback 1\n");
11947         }
11948         break;
11949     }
11950 }
11951
11952 void
11953 MoveNowEvent()
11954 {
11955     ChessProgramState *cps;
11956
11957     switch (gameMode) {
11958       case MachinePlaysWhite:
11959         if (!WhiteOnMove(forwardMostMove)) {
11960             DisplayError(_("It is your turn"), 0);
11961             return;
11962         }
11963         cps = &first;
11964         break;
11965       case MachinePlaysBlack:
11966         if (WhiteOnMove(forwardMostMove)) {
11967             DisplayError(_("It is your turn"), 0);
11968             return;
11969         }
11970         cps = &first;
11971         break;
11972       case TwoMachinesPlay:
11973         if (WhiteOnMove(forwardMostMove) ==
11974             (first.twoMachinesColor[0] == 'w')) {
11975             cps = &first;
11976         } else {
11977             cps = &second;
11978         }
11979         break;
11980       case BeginningOfGame:
11981       default:
11982         return;
11983     }
11984     SendToProgram("?\n", cps);
11985 }
11986
11987 void
11988 TruncateGameEvent()
11989 {
11990     EditGameEvent();
11991     if (gameMode != EditGame) return;
11992     TruncateGame();
11993 }
11994
11995 void
11996 TruncateGame()
11997 {
11998     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
11999     if (forwardMostMove > currentMove) {
12000         if (gameInfo.resultDetails != NULL) {
12001             free(gameInfo.resultDetails);
12002             gameInfo.resultDetails = NULL;
12003             gameInfo.result = GameUnfinished;
12004         }
12005         forwardMostMove = currentMove;
12006         HistorySet(parseList, backwardMostMove, forwardMostMove,
12007                    currentMove-1);
12008     }
12009 }
12010
12011 void
12012 HintEvent()
12013 {
12014     if (appData.noChessProgram) return;
12015     switch (gameMode) {
12016       case MachinePlaysWhite:
12017         if (WhiteOnMove(forwardMostMove)) {
12018             DisplayError(_("Wait until your turn"), 0);
12019             return;
12020         }
12021         break;
12022       case BeginningOfGame:
12023       case MachinePlaysBlack:
12024         if (!WhiteOnMove(forwardMostMove)) {
12025             DisplayError(_("Wait until your turn"), 0);
12026             return;
12027         }
12028         break;
12029       default:
12030         DisplayError(_("No hint available"), 0);
12031         return;
12032     }
12033     SendToProgram("hint\n", &first);
12034     hintRequested = TRUE;
12035 }
12036
12037 void
12038 BookEvent()
12039 {
12040     if (appData.noChessProgram) return;
12041     switch (gameMode) {
12042       case MachinePlaysWhite:
12043         if (WhiteOnMove(forwardMostMove)) {
12044             DisplayError(_("Wait until your turn"), 0);
12045             return;
12046         }
12047         break;
12048       case BeginningOfGame:
12049       case MachinePlaysBlack:
12050         if (!WhiteOnMove(forwardMostMove)) {
12051             DisplayError(_("Wait until your turn"), 0);
12052             return;
12053         }
12054         break;
12055       case EditPosition:
12056         EditPositionDone(TRUE);
12057         break;
12058       case TwoMachinesPlay:
12059         return;
12060       default:
12061         break;
12062     }
12063     SendToProgram("bk\n", &first);
12064     bookOutput[0] = NULLCHAR;
12065     bookRequested = TRUE;
12066 }
12067
12068 void
12069 AboutGameEvent()
12070 {
12071     char *tags = PGNTags(&gameInfo);
12072     TagsPopUp(tags, CmailMsg());
12073     free(tags);
12074 }
12075
12076 /* end button procedures */
12077
12078 void
12079 PrintPosition(fp, move)
12080      FILE *fp;
12081      int move;
12082 {
12083     int i, j;
12084
12085     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12086         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12087             char c = PieceToChar(boards[move][i][j]);
12088             fputc(c == 'x' ? '.' : c, fp);
12089             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12090         }
12091     }
12092     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12093       fprintf(fp, "white to play\n");
12094     else
12095       fprintf(fp, "black to play\n");
12096 }
12097
12098 void
12099 PrintOpponents(fp)
12100      FILE *fp;
12101 {
12102     if (gameInfo.white != NULL) {
12103         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12104     } else {
12105         fprintf(fp, "\n");
12106     }
12107 }
12108
12109 /* Find last component of program's own name, using some heuristics */
12110 void
12111 TidyProgramName(prog, host, buf)
12112      char *prog, *host, buf[MSG_SIZ];
12113 {
12114     char *p, *q;
12115     int local = (strcmp(host, "localhost") == 0);
12116     while (!local && (p = strchr(prog, ';')) != NULL) {
12117         p++;
12118         while (*p == ' ') p++;
12119         prog = p;
12120     }
12121     if (*prog == '"' || *prog == '\'') {
12122         q = strchr(prog + 1, *prog);
12123     } else {
12124         q = strchr(prog, ' ');
12125     }
12126     if (q == NULL) q = prog + strlen(prog);
12127     p = q;
12128     while (p >= prog && *p != '/' && *p != '\\') p--;
12129     p++;
12130     if(p == prog && *p == '"') p++;
12131     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12132     memcpy(buf, p, q - p);
12133     buf[q - p] = NULLCHAR;
12134     if (!local) {
12135         strcat(buf, "@");
12136         strcat(buf, host);
12137     }
12138 }
12139
12140 char *
12141 TimeControlTagValue()
12142 {
12143     char buf[MSG_SIZ];
12144     if (!appData.clockMode) {
12145         strcpy(buf, "-");
12146     } else if (movesPerSession > 0) {
12147         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12148     } else if (timeIncrement == 0) {
12149         sprintf(buf, "%ld", timeControl/1000);
12150     } else {
12151         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12152     }
12153     return StrSave(buf);
12154 }
12155
12156 void
12157 SetGameInfo()
12158 {
12159     /* This routine is used only for certain modes */
12160     VariantClass v = gameInfo.variant;
12161     ChessMove r = GameUnfinished;
12162     char *p = NULL;
12163
12164     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12165         r = gameInfo.result; 
12166         p = gameInfo.resultDetails; 
12167         gameInfo.resultDetails = NULL;
12168     }
12169     ClearGameInfo(&gameInfo);
12170     gameInfo.variant = v;
12171
12172     switch (gameMode) {
12173       case MachinePlaysWhite:
12174         gameInfo.event = StrSave( appData.pgnEventHeader );
12175         gameInfo.site = StrSave(HostName());
12176         gameInfo.date = PGNDate();
12177         gameInfo.round = StrSave("-");
12178         gameInfo.white = StrSave(first.tidy);
12179         gameInfo.black = StrSave(UserName());
12180         gameInfo.timeControl = TimeControlTagValue();
12181         break;
12182
12183       case MachinePlaysBlack:
12184         gameInfo.event = StrSave( appData.pgnEventHeader );
12185         gameInfo.site = StrSave(HostName());
12186         gameInfo.date = PGNDate();
12187         gameInfo.round = StrSave("-");
12188         gameInfo.white = StrSave(UserName());
12189         gameInfo.black = StrSave(first.tidy);
12190         gameInfo.timeControl = TimeControlTagValue();
12191         break;
12192
12193       case TwoMachinesPlay:
12194         gameInfo.event = StrSave( appData.pgnEventHeader );
12195         gameInfo.site = StrSave(HostName());
12196         gameInfo.date = PGNDate();
12197         if (matchGame > 0) {
12198             char buf[MSG_SIZ];
12199             sprintf(buf, "%d", matchGame);
12200             gameInfo.round = StrSave(buf);
12201         } else {
12202             gameInfo.round = StrSave("-");
12203         }
12204         if (first.twoMachinesColor[0] == 'w') {
12205             gameInfo.white = StrSave(first.tidy);
12206             gameInfo.black = StrSave(second.tidy);
12207         } else {
12208             gameInfo.white = StrSave(second.tidy);
12209             gameInfo.black = StrSave(first.tidy);
12210         }
12211         gameInfo.timeControl = TimeControlTagValue();
12212         break;
12213
12214       case EditGame:
12215         gameInfo.event = StrSave("Edited game");
12216         gameInfo.site = StrSave(HostName());
12217         gameInfo.date = PGNDate();
12218         gameInfo.round = StrSave("-");
12219         gameInfo.white = StrSave("-");
12220         gameInfo.black = StrSave("-");
12221         gameInfo.result = r;
12222         gameInfo.resultDetails = p;
12223         break;
12224
12225       case EditPosition:
12226         gameInfo.event = StrSave("Edited position");
12227         gameInfo.site = StrSave(HostName());
12228         gameInfo.date = PGNDate();
12229         gameInfo.round = StrSave("-");
12230         gameInfo.white = StrSave("-");
12231         gameInfo.black = StrSave("-");
12232         break;
12233
12234       case IcsPlayingWhite:
12235       case IcsPlayingBlack:
12236       case IcsObserving:
12237       case IcsExamining:
12238         break;
12239
12240       case PlayFromGameFile:
12241         gameInfo.event = StrSave("Game from non-PGN file");
12242         gameInfo.site = StrSave(HostName());
12243         gameInfo.date = PGNDate();
12244         gameInfo.round = StrSave("-");
12245         gameInfo.white = StrSave("?");
12246         gameInfo.black = StrSave("?");
12247         break;
12248
12249       default:
12250         break;
12251     }
12252 }
12253
12254 void
12255 ReplaceComment(index, text)
12256      int index;
12257      char *text;
12258 {
12259     int len;
12260
12261     while (*text == '\n') text++;
12262     len = strlen(text);
12263     while (len > 0 && text[len - 1] == '\n') len--;
12264
12265     if (commentList[index] != NULL)
12266       free(commentList[index]);
12267
12268     if (len == 0) {
12269         commentList[index] = NULL;
12270         return;
12271     }
12272   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12273       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12274       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12275     commentList[index] = (char *) malloc(len + 2);
12276     strncpy(commentList[index], text, len);
12277     commentList[index][len] = '\n';
12278     commentList[index][len + 1] = NULLCHAR;
12279   } else { 
12280     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12281     char *p;
12282     commentList[index] = (char *) malloc(len + 6);
12283     strcpy(commentList[index], "{\n");
12284     strncpy(commentList[index]+2, text, len);
12285     commentList[index][len+2] = NULLCHAR;
12286     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12287     strcat(commentList[index], "\n}\n");
12288   }
12289 }
12290
12291 void
12292 CrushCRs(text)
12293      char *text;
12294 {
12295   char *p = text;
12296   char *q = text;
12297   char ch;
12298
12299   do {
12300     ch = *p++;
12301     if (ch == '\r') continue;
12302     *q++ = ch;
12303   } while (ch != '\0');
12304 }
12305
12306 void
12307 AppendComment(index, text, addBraces)
12308      int index;
12309      char *text;
12310      Boolean addBraces; // [HGM] braces: tells if we should add {}
12311 {
12312     int oldlen, len;
12313     char *old;
12314
12315 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12316     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12317
12318     CrushCRs(text);
12319     while (*text == '\n') text++;
12320     len = strlen(text);
12321     while (len > 0 && text[len - 1] == '\n') len--;
12322
12323     if (len == 0) return;
12324
12325     if (commentList[index] != NULL) {
12326         old = commentList[index];
12327         oldlen = strlen(old);
12328         while(commentList[index][oldlen-1] ==  '\n')
12329           commentList[index][--oldlen] = NULLCHAR;
12330         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12331         strcpy(commentList[index], old);
12332         free(old);
12333         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12334         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12335           if(addBraces) addBraces = FALSE; else { text++; len--; }
12336           while (*text == '\n') { text++; len--; }
12337           commentList[index][--oldlen] = NULLCHAR;
12338       }
12339         if(addBraces) strcat(commentList[index], "\n{\n");
12340         else          strcat(commentList[index], "\n");
12341         strcat(commentList[index], text);
12342         if(addBraces) strcat(commentList[index], "\n}\n");
12343         else          strcat(commentList[index], "\n");
12344     } else {
12345         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12346         if(addBraces)
12347              strcpy(commentList[index], "{\n");
12348         else commentList[index][0] = NULLCHAR;
12349         strcat(commentList[index], text);
12350         strcat(commentList[index], "\n");
12351         if(addBraces) strcat(commentList[index], "}\n");
12352     }
12353 }
12354
12355 static char * FindStr( char * text, char * sub_text )
12356 {
12357     char * result = strstr( text, sub_text );
12358
12359     if( result != NULL ) {
12360         result += strlen( sub_text );
12361     }
12362
12363     return result;
12364 }
12365
12366 /* [AS] Try to extract PV info from PGN comment */
12367 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12368 char *GetInfoFromComment( int index, char * text )
12369 {
12370     char * sep = text;
12371
12372     if( text != NULL && index > 0 ) {
12373         int score = 0;
12374         int depth = 0;
12375         int time = -1, sec = 0, deci;
12376         char * s_eval = FindStr( text, "[%eval " );
12377         char * s_emt = FindStr( text, "[%emt " );
12378
12379         if( s_eval != NULL || s_emt != NULL ) {
12380             /* New style */
12381             char delim;
12382
12383             if( s_eval != NULL ) {
12384                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12385                     return text;
12386                 }
12387
12388                 if( delim != ']' ) {
12389                     return text;
12390                 }
12391             }
12392
12393             if( s_emt != NULL ) {
12394             }
12395                 return text;
12396         }
12397         else {
12398             /* We expect something like: [+|-]nnn.nn/dd */
12399             int score_lo = 0;
12400
12401             if(*text != '{') return text; // [HGM] braces: must be normal comment
12402
12403             sep = strchr( text, '/' );
12404             if( sep == NULL || sep < (text+4) ) {
12405                 return text;
12406             }
12407
12408             time = -1; sec = -1; deci = -1;
12409             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12410                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12411                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12412                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12413                 return text;
12414             }
12415
12416             if( score_lo < 0 || score_lo >= 100 ) {
12417                 return text;
12418             }
12419
12420             if(sec >= 0) time = 600*time + 10*sec; else
12421             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12422
12423             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12424
12425             /* [HGM] PV time: now locate end of PV info */
12426             while( *++sep >= '0' && *sep <= '9'); // strip depth
12427             if(time >= 0)
12428             while( *++sep >= '0' && *sep <= '9'); // strip time
12429             if(sec >= 0)
12430             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12431             if(deci >= 0)
12432             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12433             while(*sep == ' ') sep++;
12434         }
12435
12436         if( depth <= 0 ) {
12437             return text;
12438         }
12439
12440         if( time < 0 ) {
12441             time = -1;
12442         }
12443
12444         pvInfoList[index-1].depth = depth;
12445         pvInfoList[index-1].score = score;
12446         pvInfoList[index-1].time  = 10*time; // centi-sec
12447         if(*sep == '}') *sep = 0; else *--sep = '{';
12448     }
12449     return sep;
12450 }
12451
12452 void
12453 SendToProgram(message, cps)
12454      char *message;
12455      ChessProgramState *cps;
12456 {
12457     int count, outCount, error;
12458     char buf[MSG_SIZ];
12459
12460     if (cps->pr == NULL) return;
12461     Attention(cps);
12462
12463     if (appData.debugMode) {
12464         TimeMark now;
12465         GetTimeMark(&now);
12466         fprintf(debugFP, "%ld >%-6s: %s",
12467                 SubtractTimeMarks(&now, &programStartTime),
12468                 cps->which, message);
12469     }
12470
12471     count = strlen(message);
12472     outCount = OutputToProcess(cps->pr, message, count, &error);
12473     if (outCount < count && !exiting
12474                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12475         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12476         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12477             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12478                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12479                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12480             } else {
12481                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12482             }
12483             gameInfo.resultDetails = StrSave(buf);
12484         }
12485         DisplayFatalError(buf, error, 1);
12486     }
12487 }
12488
12489 void
12490 ReceiveFromProgram(isr, closure, message, count, error)
12491      InputSourceRef isr;
12492      VOIDSTAR closure;
12493      char *message;
12494      int count;
12495      int error;
12496 {
12497     char *end_str;
12498     char buf[MSG_SIZ];
12499     ChessProgramState *cps = (ChessProgramState *)closure;
12500
12501     if (isr != cps->isr) return; /* Killed intentionally */
12502     if (count <= 0) {
12503         if (count == 0) {
12504             sprintf(buf,
12505                     _("Error: %s chess program (%s) exited unexpectedly"),
12506                     cps->which, cps->program);
12507         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12508                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12509                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12510                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12511                 } else {
12512                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12513                 }
12514                 gameInfo.resultDetails = StrSave(buf);
12515             }
12516             RemoveInputSource(cps->isr);
12517             DisplayFatalError(buf, 0, 1);
12518         } else {
12519             sprintf(buf,
12520                     _("Error reading from %s chess program (%s)"),
12521                     cps->which, cps->program);
12522             RemoveInputSource(cps->isr);
12523
12524             /* [AS] Program is misbehaving badly... kill it */
12525             if( count == -2 ) {
12526                 DestroyChildProcess( cps->pr, 9 );
12527                 cps->pr = NoProc;
12528             }
12529
12530             DisplayFatalError(buf, error, 1);
12531         }
12532         return;
12533     }
12534
12535     if ((end_str = strchr(message, '\r')) != NULL)
12536       *end_str = NULLCHAR;
12537     if ((end_str = strchr(message, '\n')) != NULL)
12538       *end_str = NULLCHAR;
12539
12540     if (appData.debugMode) {
12541         TimeMark now; int print = 1;
12542         char *quote = ""; char c; int i;
12543
12544         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12545                 char start = message[0];
12546                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12547                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12548                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12549                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12550                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12551                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12552                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12553                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12554                         { quote = "# "; print = (appData.engineComments == 2); }
12555                 message[0] = start; // restore original message
12556         }
12557         if(print) {
12558                 GetTimeMark(&now);
12559                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12560                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12561                         quote,
12562                         message);
12563         }
12564     }
12565
12566     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12567     if (appData.icsEngineAnalyze) {
12568         if (strstr(message, "whisper") != NULL ||
12569              strstr(message, "kibitz") != NULL ||
12570             strstr(message, "tellics") != NULL) return;
12571     }
12572
12573     HandleMachineMove(message, cps);
12574 }
12575
12576
12577 void
12578 SendTimeControl(cps, mps, tc, inc, sd, st)
12579      ChessProgramState *cps;
12580      int mps, inc, sd, st;
12581      long tc;
12582 {
12583     char buf[MSG_SIZ];
12584     int seconds;
12585
12586     if( timeControl_2 > 0 ) {
12587         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12588             tc = timeControl_2;
12589         }
12590     }
12591     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12592     inc /= cps->timeOdds;
12593     st  /= cps->timeOdds;
12594
12595     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12596
12597     if (st > 0) {
12598       /* Set exact time per move, normally using st command */
12599       if (cps->stKludge) {
12600         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12601         seconds = st % 60;
12602         if (seconds == 0) {
12603           sprintf(buf, "level 1 %d\n", st/60);
12604         } else {
12605           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12606         }
12607       } else {
12608         sprintf(buf, "st %d\n", st);
12609       }
12610     } else {
12611       /* Set conventional or incremental time control, using level command */
12612       if (seconds == 0) {
12613         /* Note old gnuchess bug -- minutes:seconds used to not work.
12614            Fixed in later versions, but still avoid :seconds
12615            when seconds is 0. */
12616         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12617       } else {
12618         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12619                 seconds, inc/1000);
12620       }
12621     }
12622     SendToProgram(buf, cps);
12623
12624     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12625     /* Orthogonally, limit search to given depth */
12626     if (sd > 0) {
12627       if (cps->sdKludge) {
12628         sprintf(buf, "depth\n%d\n", sd);
12629       } else {
12630         sprintf(buf, "sd %d\n", sd);
12631       }
12632       SendToProgram(buf, cps);
12633     }
12634
12635     if(cps->nps > 0) { /* [HGM] nps */
12636         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12637         else {
12638                 sprintf(buf, "nps %d\n", cps->nps);
12639               SendToProgram(buf, cps);
12640         }
12641     }
12642 }
12643
12644 ChessProgramState *WhitePlayer()
12645 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12646 {
12647     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12648        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12649         return &second;
12650     return &first;
12651 }
12652
12653 void
12654 SendTimeRemaining(cps, machineWhite)
12655      ChessProgramState *cps;
12656      int /*boolean*/ machineWhite;
12657 {
12658     char message[MSG_SIZ];
12659     long time, otime;
12660
12661     /* Note: this routine must be called when the clocks are stopped
12662        or when they have *just* been set or switched; otherwise
12663        it will be off by the time since the current tick started.
12664     */
12665     if (machineWhite) {
12666         time = whiteTimeRemaining / 10;
12667         otime = blackTimeRemaining / 10;
12668     } else {
12669         time = blackTimeRemaining / 10;
12670         otime = whiteTimeRemaining / 10;
12671     }
12672     /* [HGM] translate opponent's time by time-odds factor */
12673     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12674     if (appData.debugMode) {
12675         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12676     }
12677
12678     if (time <= 0) time = 1;
12679     if (otime <= 0) otime = 1;
12680
12681     sprintf(message, "time %ld\n", time);
12682     SendToProgram(message, cps);
12683
12684     sprintf(message, "otim %ld\n", otime);
12685     SendToProgram(message, cps);
12686 }
12687
12688 int
12689 BoolFeature(p, name, loc, cps)
12690      char **p;
12691      char *name;
12692      int *loc;
12693      ChessProgramState *cps;
12694 {
12695   char buf[MSG_SIZ];
12696   int len = strlen(name);
12697   int val;
12698   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12699     (*p) += len + 1;
12700     sscanf(*p, "%d", &val);
12701     *loc = (val != 0);
12702     while (**p && **p != ' ') (*p)++;
12703     sprintf(buf, "accepted %s\n", name);
12704     SendToProgram(buf, cps);
12705     return TRUE;
12706   }
12707   return FALSE;
12708 }
12709
12710 int
12711 IntFeature(p, name, loc, cps)
12712      char **p;
12713      char *name;
12714      int *loc;
12715      ChessProgramState *cps;
12716 {
12717   char buf[MSG_SIZ];
12718   int len = strlen(name);
12719   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12720     (*p) += len + 1;
12721     sscanf(*p, "%d", loc);
12722     while (**p && **p != ' ') (*p)++;
12723     sprintf(buf, "accepted %s\n", name);
12724     SendToProgram(buf, cps);
12725     return TRUE;
12726   }
12727   return FALSE;
12728 }
12729
12730 int
12731 StringFeature(p, name, loc, cps)
12732      char **p;
12733      char *name;
12734      char loc[];
12735      ChessProgramState *cps;
12736 {
12737   char buf[MSG_SIZ];
12738   int len = strlen(name);
12739   if (strncmp((*p), name, len) == 0
12740       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12741     (*p) += len + 2;
12742     sscanf(*p, "%[^\"]", loc);
12743     while (**p && **p != '\"') (*p)++;
12744     if (**p == '\"') (*p)++;
12745     sprintf(buf, "accepted %s\n", name);
12746     SendToProgram(buf, cps);
12747     return TRUE;
12748   }
12749   return FALSE;
12750 }
12751
12752 int
12753 ParseOption(Option *opt, ChessProgramState *cps)
12754 // [HGM] options: process the string that defines an engine option, and determine
12755 // name, type, default value, and allowed value range
12756 {
12757         char *p, *q, buf[MSG_SIZ];
12758         int n, min = (-1)<<31, max = 1<<31, def;
12759
12760         if(p = strstr(opt->name, " -spin ")) {
12761             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12762             if(max < min) max = min; // enforce consistency
12763             if(def < min) def = min;
12764             if(def > max) def = max;
12765             opt->value = def;
12766             opt->min = min;
12767             opt->max = max;
12768             opt->type = Spin;
12769         } else if((p = strstr(opt->name, " -slider "))) {
12770             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12771             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12772             if(max < min) max = min; // enforce consistency
12773             if(def < min) def = min;
12774             if(def > max) def = max;
12775             opt->value = def;
12776             opt->min = min;
12777             opt->max = max;
12778             opt->type = Spin; // Slider;
12779         } else if((p = strstr(opt->name, " -string "))) {
12780             opt->textValue = p+9;
12781             opt->type = TextBox;
12782         } else if((p = strstr(opt->name, " -file "))) {
12783             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12784             opt->textValue = p+7;
12785             opt->type = TextBox; // FileName;
12786         } else if((p = strstr(opt->name, " -path "))) {
12787             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12788             opt->textValue = p+7;
12789             opt->type = TextBox; // PathName;
12790         } else if(p = strstr(opt->name, " -check ")) {
12791             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12792             opt->value = (def != 0);
12793             opt->type = CheckBox;
12794         } else if(p = strstr(opt->name, " -combo ")) {
12795             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12796             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12797             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12798             opt->value = n = 0;
12799             while(q = StrStr(q, " /// ")) {
12800                 n++; *q = 0;    // count choices, and null-terminate each of them
12801                 q += 5;
12802                 if(*q == '*') { // remember default, which is marked with * prefix
12803                     q++;
12804                     opt->value = n;
12805                 }
12806                 cps->comboList[cps->comboCnt++] = q;
12807             }
12808             cps->comboList[cps->comboCnt++] = NULL;
12809             opt->max = n + 1;
12810             opt->type = ComboBox;
12811         } else if(p = strstr(opt->name, " -button")) {
12812             opt->type = Button;
12813         } else if(p = strstr(opt->name, " -save")) {
12814             opt->type = SaveButton;
12815         } else return FALSE;
12816         *p = 0; // terminate option name
12817         // now look if the command-line options define a setting for this engine option.
12818         if(cps->optionSettings && cps->optionSettings[0])
12819             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12820         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12821                 sprintf(buf, "option %s", p);
12822                 if(p = strstr(buf, ",")) *p = 0;
12823                 strcat(buf, "\n");
12824                 SendToProgram(buf, cps);
12825         }
12826         return TRUE;
12827 }
12828
12829 void
12830 FeatureDone(cps, val)
12831      ChessProgramState* cps;
12832      int val;
12833 {
12834   DelayedEventCallback cb = GetDelayedEvent();
12835   if ((cb == InitBackEnd3 && cps == &first) ||
12836       (cb == TwoMachinesEventIfReady && cps == &second)) {
12837     CancelDelayedEvent();
12838     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12839   }
12840   cps->initDone = val;
12841 }
12842
12843 /* Parse feature command from engine */
12844 void
12845 ParseFeatures(args, cps)
12846      char* args;
12847      ChessProgramState *cps;
12848 {
12849   char *p = args;
12850   char *q;
12851   int val;
12852   char buf[MSG_SIZ];
12853
12854   for (;;) {
12855     while (*p == ' ') p++;
12856     if (*p == NULLCHAR) return;
12857
12858     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12859     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12860     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12861     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12862     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12863     if (BoolFeature(&p, "reuse", &val, cps)) {
12864       /* Engine can disable reuse, but can't enable it if user said no */
12865       if (!val) cps->reuse = FALSE;
12866       continue;
12867     }
12868     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12869     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12870       if (gameMode == TwoMachinesPlay) {
12871         DisplayTwoMachinesTitle();
12872       } else {
12873         DisplayTitle("");
12874       }
12875       continue;
12876     }
12877     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12878     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12879     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12880     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12881     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12882     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12883     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12884     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12885     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12886     if (IntFeature(&p, "done", &val, cps)) {
12887       FeatureDone(cps, val);
12888       continue;
12889     }
12890     /* Added by Tord: */
12891     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12892     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12893     /* End of additions by Tord */
12894
12895     /* [HGM] added features: */
12896     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12897     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12898     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12899     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12900     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12901     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12902     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12903         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12904             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12905             SendToProgram(buf, cps);
12906             continue;
12907         }
12908         if(cps->nrOptions >= MAX_OPTIONS) {
12909             cps->nrOptions--;
12910             sprintf(buf, "%s engine has too many options\n", cps->which);
12911             DisplayError(buf, 0);
12912         }
12913         continue;
12914     }
12915     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12916     /* End of additions by HGM */
12917
12918     /* unknown feature: complain and skip */
12919     q = p;
12920     while (*q && *q != '=') q++;
12921     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12922     SendToProgram(buf, cps);
12923     p = q;
12924     if (*p == '=') {
12925       p++;
12926       if (*p == '\"') {
12927         p++;
12928         while (*p && *p != '\"') p++;
12929         if (*p == '\"') p++;
12930       } else {
12931         while (*p && *p != ' ') p++;
12932       }
12933     }
12934   }
12935
12936 }
12937
12938 void
12939 PeriodicUpdatesEvent(newState)
12940      int newState;
12941 {
12942     if (newState == appData.periodicUpdates)
12943       return;
12944
12945     appData.periodicUpdates=newState;
12946
12947     /* Display type changes, so update it now */
12948 //    DisplayAnalysis();
12949
12950     /* Get the ball rolling again... */
12951     if (newState) {
12952         AnalysisPeriodicEvent(1);
12953         StartAnalysisClock();
12954     }
12955 }
12956
12957 void
12958 PonderNextMoveEvent(newState)
12959      int newState;
12960 {
12961     if (newState == appData.ponderNextMove) return;
12962     if (gameMode == EditPosition) EditPositionDone(TRUE);
12963     if (newState) {
12964         SendToProgram("hard\n", &first);
12965         if (gameMode == TwoMachinesPlay) {
12966             SendToProgram("hard\n", &second);
12967         }
12968     } else {
12969         SendToProgram("easy\n", &first);
12970         thinkOutput[0] = NULLCHAR;
12971         if (gameMode == TwoMachinesPlay) {
12972             SendToProgram("easy\n", &second);
12973         }
12974     }
12975     appData.ponderNextMove = newState;
12976 }
12977
12978 void
12979 NewSettingEvent(option, command, value)
12980      char *command;
12981      int option, value;
12982 {
12983     char buf[MSG_SIZ];
12984
12985     if (gameMode == EditPosition) EditPositionDone(TRUE);
12986     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12987     SendToProgram(buf, &first);
12988     if (gameMode == TwoMachinesPlay) {
12989         SendToProgram(buf, &second);
12990     }
12991 }
12992
12993 void
12994 ShowThinkingEvent()
12995 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12996 {
12997     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12998     int newState = appData.showThinking
12999         // [HGM] thinking: other features now need thinking output as well
13000         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13001
13002     if (oldState == newState) return;
13003     oldState = newState;
13004     if (gameMode == EditPosition) EditPositionDone(TRUE);
13005     if (oldState) {
13006         SendToProgram("post\n", &first);
13007         if (gameMode == TwoMachinesPlay) {
13008             SendToProgram("post\n", &second);
13009         }
13010     } else {
13011         SendToProgram("nopost\n", &first);
13012         thinkOutput[0] = NULLCHAR;
13013         if (gameMode == TwoMachinesPlay) {
13014             SendToProgram("nopost\n", &second);
13015         }
13016     }
13017 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13018 }
13019
13020 void
13021 AskQuestionEvent(title, question, replyPrefix, which)
13022      char *title; char *question; char *replyPrefix; char *which;
13023 {
13024   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13025   if (pr == NoProc) return;
13026   AskQuestion(title, question, replyPrefix, pr);
13027 }
13028
13029 void
13030 DisplayMove(moveNumber)
13031      int moveNumber;
13032 {
13033     char message[MSG_SIZ];
13034     char res[MSG_SIZ];
13035     char cpThinkOutput[MSG_SIZ];
13036
13037     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13038
13039     if (moveNumber == forwardMostMove - 1 ||
13040         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13041
13042         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13043
13044         if (strchr(cpThinkOutput, '\n')) {
13045             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13046         }
13047     } else {
13048         *cpThinkOutput = NULLCHAR;
13049     }
13050
13051     /* [AS] Hide thinking from human user */
13052     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13053         *cpThinkOutput = NULLCHAR;
13054         if( thinkOutput[0] != NULLCHAR ) {
13055             int i;
13056
13057             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13058                 cpThinkOutput[i] = '.';
13059             }
13060             cpThinkOutput[i] = NULLCHAR;
13061             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13062         }
13063     }
13064
13065     if (moveNumber == forwardMostMove - 1 &&
13066         gameInfo.resultDetails != NULL) {
13067         if (gameInfo.resultDetails[0] == NULLCHAR) {
13068             sprintf(res, " %s", PGNResult(gameInfo.result));
13069         } else {
13070             sprintf(res, " {%s} %s",
13071                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13072         }
13073     } else {
13074         res[0] = NULLCHAR;
13075     }
13076
13077     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13078         DisplayMessage(res, cpThinkOutput);
13079     } else {
13080         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13081                 WhiteOnMove(moveNumber) ? " " : ".. ",
13082                 parseList[moveNumber], res);
13083         DisplayMessage(message, cpThinkOutput);
13084     }
13085 }
13086
13087 void
13088 DisplayComment(moveNumber, text)
13089      int moveNumber;
13090      char *text;
13091 {
13092     char title[MSG_SIZ];
13093     char buf[8000]; // comment can be long!
13094     int score, depth;
13095     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13096       strcpy(title, "Comment");
13097     } else {
13098       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13099               WhiteOnMove(moveNumber) ? " " : ".. ",
13100               parseList[moveNumber]);
13101     }
13102     // [HGM] PV info: display PV info together with (or as) comment
13103     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13104       if(text == NULL) text = "";                                           
13105       score = pvInfoList[moveNumber].score;
13106       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13107               depth, (pvInfoList[moveNumber].time+50)/100, text);
13108       text = buf;
13109     }
13110     if (text != NULL && (appData.autoDisplayComment || commentUp))
13111       CommentPopUp(title, text);
13112 }
13113
13114 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13115  * might be busy thinking or pondering.  It can be omitted if your
13116  * gnuchess is configured to stop thinking immediately on any user
13117  * input.  However, that gnuchess feature depends on the FIONREAD
13118  * ioctl, which does not work properly on some flavors of Unix.
13119  */
13120 void
13121 Attention(cps)
13122      ChessProgramState *cps;
13123 {
13124 #if ATTENTION
13125     if (!cps->useSigint) return;
13126     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13127     switch (gameMode) {
13128       case MachinePlaysWhite:
13129       case MachinePlaysBlack:
13130       case TwoMachinesPlay:
13131       case IcsPlayingWhite:
13132       case IcsPlayingBlack:
13133       case AnalyzeMode:
13134       case AnalyzeFile:
13135         /* Skip if we know it isn't thinking */
13136         if (!cps->maybeThinking) return;
13137         if (appData.debugMode)
13138           fprintf(debugFP, "Interrupting %s\n", cps->which);
13139         InterruptChildProcess(cps->pr);
13140         cps->maybeThinking = FALSE;
13141         break;
13142       default:
13143         break;
13144     }
13145 #endif /*ATTENTION*/
13146 }
13147
13148 int
13149 CheckFlags()
13150 {
13151     if (whiteTimeRemaining <= 0) {
13152         if (!whiteFlag) {
13153             whiteFlag = TRUE;
13154             if (appData.icsActive) {
13155                 if (appData.autoCallFlag &&
13156                     gameMode == IcsPlayingBlack && !blackFlag) {
13157                   SendToICS(ics_prefix);
13158                   SendToICS("flag\n");
13159                 }
13160             } else {
13161                 if (blackFlag) {
13162                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13163                 } else {
13164                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13165                     if (appData.autoCallFlag) {
13166                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13167                         return TRUE;
13168                     }
13169                 }
13170             }
13171         }
13172     }
13173     if (blackTimeRemaining <= 0) {
13174         if (!blackFlag) {
13175             blackFlag = TRUE;
13176             if (appData.icsActive) {
13177                 if (appData.autoCallFlag &&
13178                     gameMode == IcsPlayingWhite && !whiteFlag) {
13179                   SendToICS(ics_prefix);
13180                   SendToICS("flag\n");
13181                 }
13182             } else {
13183                 if (whiteFlag) {
13184                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13185                 } else {
13186                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13187                     if (appData.autoCallFlag) {
13188                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13189                         return TRUE;
13190                     }
13191                 }
13192             }
13193         }
13194     }
13195     return FALSE;
13196 }
13197
13198 void
13199 CheckTimeControl()
13200 {
13201     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13202         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13203
13204     /*
13205      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13206      */
13207     if ( !WhiteOnMove(forwardMostMove) )
13208         /* White made time control */
13209         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13210         /* [HGM] time odds: correct new time quota for time odds! */
13211                                             / WhitePlayer()->timeOdds;
13212       else
13213         /* Black made time control */
13214         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13215                                             / WhitePlayer()->other->timeOdds;
13216 }
13217
13218 void
13219 DisplayBothClocks()
13220 {
13221     int wom = gameMode == EditPosition ?
13222       !blackPlaysFirst : WhiteOnMove(currentMove);
13223     DisplayWhiteClock(whiteTimeRemaining, wom);
13224     DisplayBlackClock(blackTimeRemaining, !wom);
13225 }
13226
13227
13228 /* Timekeeping seems to be a portability nightmare.  I think everyone
13229    has ftime(), but I'm really not sure, so I'm including some ifdefs
13230    to use other calls if you don't.  Clocks will be less accurate if
13231    you have neither ftime nor gettimeofday.
13232 */
13233
13234 /* VS 2008 requires the #include outside of the function */
13235 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13236 #include <sys/timeb.h>
13237 #endif
13238
13239 /* Get the current time as a TimeMark */
13240 void
13241 GetTimeMark(tm)
13242      TimeMark *tm;
13243 {
13244 #if HAVE_GETTIMEOFDAY
13245
13246     struct timeval timeVal;
13247     struct timezone timeZone;
13248
13249     gettimeofday(&timeVal, &timeZone);
13250     tm->sec = (long) timeVal.tv_sec;
13251     tm->ms = (int) (timeVal.tv_usec / 1000L);
13252
13253 #else /*!HAVE_GETTIMEOFDAY*/
13254 #if HAVE_FTIME
13255
13256 // include <sys/timeb.h> / moved to just above start of function
13257     struct timeb timeB;
13258
13259     ftime(&timeB);
13260     tm->sec = (long) timeB.time;
13261     tm->ms = (int) timeB.millitm;
13262
13263 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13264     tm->sec = (long) time(NULL);
13265     tm->ms = 0;
13266 #endif
13267 #endif
13268 }
13269
13270 /* Return the difference in milliseconds between two
13271    time marks.  We assume the difference will fit in a long!
13272 */
13273 long
13274 SubtractTimeMarks(tm2, tm1)
13275      TimeMark *tm2, *tm1;
13276 {
13277     return 1000L*(tm2->sec - tm1->sec) +
13278            (long) (tm2->ms - tm1->ms);
13279 }
13280
13281
13282 /*
13283  * Code to manage the game clocks.
13284  *
13285  * In tournament play, black starts the clock and then white makes a move.
13286  * We give the human user a slight advantage if he is playing white---the
13287  * clocks don't run until he makes his first move, so it takes zero time.
13288  * Also, we don't account for network lag, so we could get out of sync
13289  * with GNU Chess's clock -- but then, referees are always right.
13290  */
13291
13292 static TimeMark tickStartTM;
13293 static long intendedTickLength;
13294
13295 long
13296 NextTickLength(timeRemaining)
13297      long timeRemaining;
13298 {
13299     long nominalTickLength, nextTickLength;
13300
13301     if (timeRemaining > 0L && timeRemaining <= 10000L)
13302       nominalTickLength = 100L;
13303     else
13304       nominalTickLength = 1000L;
13305     nextTickLength = timeRemaining % nominalTickLength;
13306     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13307
13308     return nextTickLength;
13309 }
13310
13311 /* Adjust clock one minute up or down */
13312 void
13313 AdjustClock(Boolean which, int dir)
13314 {
13315     if(which) blackTimeRemaining += 60000*dir;
13316     else      whiteTimeRemaining += 60000*dir;
13317     DisplayBothClocks();
13318 }
13319
13320 /* Stop clocks and reset to a fresh time control */
13321 void
13322 ResetClocks()
13323 {
13324     (void) StopClockTimer();
13325     if (appData.icsActive) {
13326         whiteTimeRemaining = blackTimeRemaining = 0;
13327     } else if (searchTime) {
13328         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13329         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13330     } else { /* [HGM] correct new time quote for time odds */
13331         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13332         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13333     }
13334     if (whiteFlag || blackFlag) {
13335         DisplayTitle("");
13336         whiteFlag = blackFlag = FALSE;
13337     }
13338     DisplayBothClocks();
13339 }
13340
13341 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13342
13343 /* Decrement running clock by amount of time that has passed */
13344 void
13345 DecrementClocks()
13346 {
13347     long timeRemaining;
13348     long lastTickLength, fudge;
13349     TimeMark now;
13350
13351     if (!appData.clockMode) return;
13352     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13353
13354     GetTimeMark(&now);
13355
13356     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13357
13358     /* Fudge if we woke up a little too soon */
13359     fudge = intendedTickLength - lastTickLength;
13360     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13361
13362     if (WhiteOnMove(forwardMostMove)) {
13363         if(whiteNPS >= 0) lastTickLength = 0;
13364         timeRemaining = whiteTimeRemaining -= lastTickLength;
13365         DisplayWhiteClock(whiteTimeRemaining - fudge,
13366                           WhiteOnMove(currentMove));
13367     } else {
13368         if(blackNPS >= 0) lastTickLength = 0;
13369         timeRemaining = blackTimeRemaining -= lastTickLength;
13370         DisplayBlackClock(blackTimeRemaining - fudge,
13371                           !WhiteOnMove(currentMove));
13372     }
13373
13374     if (CheckFlags()) return;
13375
13376     tickStartTM = now;
13377     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13378     StartClockTimer(intendedTickLength);
13379
13380     /* if the time remaining has fallen below the alarm threshold, sound the
13381      * alarm. if the alarm has sounded and (due to a takeback or time control
13382      * with increment) the time remaining has increased to a level above the
13383      * threshold, reset the alarm so it can sound again.
13384      */
13385
13386     if (appData.icsActive && appData.icsAlarm) {
13387
13388         /* make sure we are dealing with the user's clock */
13389         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13390                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13391            )) return;
13392
13393         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13394             alarmSounded = FALSE;
13395         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13396             PlayAlarmSound();
13397             alarmSounded = TRUE;
13398         }
13399     }
13400 }
13401
13402
13403 /* A player has just moved, so stop the previously running
13404    clock and (if in clock mode) start the other one.
13405    We redisplay both clocks in case we're in ICS mode, because
13406    ICS gives us an update to both clocks after every move.
13407    Note that this routine is called *after* forwardMostMove
13408    is updated, so the last fractional tick must be subtracted
13409    from the color that is *not* on move now.
13410 */
13411 void
13412 SwitchClocks()
13413 {
13414     long lastTickLength;
13415     TimeMark now;
13416     int flagged = FALSE;
13417
13418     GetTimeMark(&now);
13419
13420     if (StopClockTimer() && appData.clockMode) {
13421         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13422         if (WhiteOnMove(forwardMostMove)) {
13423             if(blackNPS >= 0) lastTickLength = 0;
13424             blackTimeRemaining -= lastTickLength;
13425            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13426 //         if(pvInfoList[forwardMostMove-1].time == -1)
13427                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13428                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13429         } else {
13430            if(whiteNPS >= 0) lastTickLength = 0;
13431            whiteTimeRemaining -= lastTickLength;
13432            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13433 //         if(pvInfoList[forwardMostMove-1].time == -1)
13434                  pvInfoList[forwardMostMove-1].time =
13435                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13436         }
13437         flagged = CheckFlags();
13438     }
13439     CheckTimeControl();
13440
13441     if (flagged || !appData.clockMode) return;
13442
13443     switch (gameMode) {
13444       case MachinePlaysBlack:
13445       case MachinePlaysWhite:
13446       case BeginningOfGame:
13447         if (pausing) return;
13448         break;
13449
13450       case EditGame:
13451       case PlayFromGameFile:
13452       case IcsExamining:
13453         return;
13454
13455       default:
13456         break;
13457     }
13458
13459     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13460         if(WhiteOnMove(forwardMostMove))
13461              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13462         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13463     }
13464
13465     tickStartTM = now;
13466     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13467       whiteTimeRemaining : blackTimeRemaining);
13468     StartClockTimer(intendedTickLength);
13469 }
13470
13471
13472 /* Stop both clocks */
13473 void
13474 StopClocks()
13475 {
13476     long lastTickLength;
13477     TimeMark now;
13478
13479     if (!StopClockTimer()) return;
13480     if (!appData.clockMode) return;
13481
13482     GetTimeMark(&now);
13483
13484     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13485     if (WhiteOnMove(forwardMostMove)) {
13486         if(whiteNPS >= 0) lastTickLength = 0;
13487         whiteTimeRemaining -= lastTickLength;
13488         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13489     } else {
13490         if(blackNPS >= 0) lastTickLength = 0;
13491         blackTimeRemaining -= lastTickLength;
13492         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13493     }
13494     CheckFlags();
13495 }
13496
13497 /* Start clock of player on move.  Time may have been reset, so
13498    if clock is already running, stop and restart it. */
13499 void
13500 StartClocks()
13501 {
13502     (void) StopClockTimer(); /* in case it was running already */
13503     DisplayBothClocks();
13504     if (CheckFlags()) return;
13505
13506     if (!appData.clockMode) return;
13507     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13508
13509     GetTimeMark(&tickStartTM);
13510     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13511       whiteTimeRemaining : blackTimeRemaining);
13512
13513    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13514     whiteNPS = blackNPS = -1;
13515     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13516        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13517         whiteNPS = first.nps;
13518     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13519        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13520         blackNPS = first.nps;
13521     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13522         whiteNPS = second.nps;
13523     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13524         blackNPS = second.nps;
13525     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13526
13527     StartClockTimer(intendedTickLength);
13528 }
13529
13530 char *
13531 TimeString(ms)
13532      long ms;
13533 {
13534     long second, minute, hour, day;
13535     char *sign = "";
13536     static char buf[32];
13537
13538     if (ms > 0 && ms <= 9900) {
13539       /* convert milliseconds to tenths, rounding up */
13540       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13541
13542       sprintf(buf, " %03.1f ", tenths/10.0);
13543       return buf;
13544     }
13545
13546     /* convert milliseconds to seconds, rounding up */
13547     /* use floating point to avoid strangeness of integer division
13548        with negative dividends on many machines */
13549     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13550
13551     if (second < 0) {
13552         sign = "-";
13553         second = -second;
13554     }
13555
13556     day = second / (60 * 60 * 24);
13557     second = second % (60 * 60 * 24);
13558     hour = second / (60 * 60);
13559     second = second % (60 * 60);
13560     minute = second / 60;
13561     second = second % 60;
13562
13563     if (day > 0)
13564       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13565               sign, day, hour, minute, second);
13566     else if (hour > 0)
13567       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13568     else
13569       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13570
13571     return buf;
13572 }
13573
13574
13575 /*
13576  * This is necessary because some C libraries aren't ANSI C compliant yet.
13577  */
13578 char *
13579 StrStr(string, match)
13580      char *string, *match;
13581 {
13582     int i, length;
13583
13584     length = strlen(match);
13585
13586     for (i = strlen(string) - length; i >= 0; i--, string++)
13587       if (!strncmp(match, string, length))
13588         return string;
13589
13590     return NULL;
13591 }
13592
13593 char *
13594 StrCaseStr(string, match)
13595      char *string, *match;
13596 {
13597     int i, j, length;
13598
13599     length = strlen(match);
13600
13601     for (i = strlen(string) - length; i >= 0; i--, string++) {
13602         for (j = 0; j < length; j++) {
13603             if (ToLower(match[j]) != ToLower(string[j]))
13604               break;
13605         }
13606         if (j == length) return string;
13607     }
13608
13609     return NULL;
13610 }
13611
13612 #ifndef _amigados
13613 int
13614 StrCaseCmp(s1, s2)
13615      char *s1, *s2;
13616 {
13617     char c1, c2;
13618
13619     for (;;) {
13620         c1 = ToLower(*s1++);
13621         c2 = ToLower(*s2++);
13622         if (c1 > c2) return 1;
13623         if (c1 < c2) return -1;
13624         if (c1 == NULLCHAR) return 0;
13625     }
13626 }
13627
13628
13629 int
13630 ToLower(c)
13631      int c;
13632 {
13633     return isupper(c) ? tolower(c) : c;
13634 }
13635
13636
13637 int
13638 ToUpper(c)
13639      int c;
13640 {
13641     return islower(c) ? toupper(c) : c;
13642 }
13643 #endif /* !_amigados    */
13644
13645 char *
13646 StrSave(s)
13647      char *s;
13648 {
13649     char *ret;
13650
13651     if ((ret = (char *) malloc(strlen(s) + 1))) {
13652         strcpy(ret, s);
13653     }
13654     return ret;
13655 }
13656
13657 char *
13658 StrSavePtr(s, savePtr)
13659      char *s, **savePtr;
13660 {
13661     if (*savePtr) {
13662         free(*savePtr);
13663     }
13664     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13665         strcpy(*savePtr, s);
13666     }
13667     return(*savePtr);
13668 }
13669
13670 char *
13671 PGNDate()
13672 {
13673     time_t clock;
13674     struct tm *tm;
13675     char buf[MSG_SIZ];
13676
13677     clock = time((time_t *)NULL);
13678     tm = localtime(&clock);
13679     sprintf(buf, "%04d.%02d.%02d",
13680             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13681     return StrSave(buf);
13682 }
13683
13684
13685 char *
13686 PositionToFEN(move, overrideCastling)
13687      int move;
13688      char *overrideCastling;
13689 {
13690     int i, j, fromX, fromY, toX, toY;
13691     int whiteToPlay;
13692     char buf[128];
13693     char *p, *q;
13694     int emptycount;
13695     ChessSquare piece;
13696
13697     whiteToPlay = (gameMode == EditPosition) ?
13698       !blackPlaysFirst : (move % 2 == 0);
13699     p = buf;
13700
13701     /* Piece placement data */
13702     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13703         emptycount = 0;
13704         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13705             if (boards[move][i][j] == EmptySquare) {
13706                 emptycount++;
13707             } else { ChessSquare piece = boards[move][i][j];
13708                 if (emptycount > 0) {
13709                     if(emptycount<10) /* [HGM] can be >= 10 */
13710                         *p++ = '0' + emptycount;
13711                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13712                     emptycount = 0;
13713                 }
13714                 if(PieceToChar(piece) == '+') {
13715                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13716                     *p++ = '+';
13717                     piece = (ChessSquare)(DEMOTED piece);
13718                 }
13719                 *p++ = PieceToChar(piece);
13720                 if(p[-1] == '~') {
13721                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13722                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13723                     *p++ = '~';
13724                 }
13725             }
13726         }
13727         if (emptycount > 0) {
13728             if(emptycount<10) /* [HGM] can be >= 10 */
13729                 *p++ = '0' + emptycount;
13730             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13731             emptycount = 0;
13732         }
13733         *p++ = '/';
13734     }
13735     *(p - 1) = ' ';
13736
13737     /* [HGM] print Crazyhouse or Shogi holdings */
13738     if( gameInfo.holdingsWidth ) {
13739         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13740         q = p;
13741         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13742             piece = boards[move][i][BOARD_WIDTH-1];
13743             if( piece != EmptySquare )
13744               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13745                   *p++ = PieceToChar(piece);
13746         }
13747         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13748             piece = boards[move][BOARD_HEIGHT-i-1][0];
13749             if( piece != EmptySquare )
13750               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13751                   *p++ = PieceToChar(piece);
13752         }
13753
13754         if( q == p ) *p++ = '-';
13755         *p++ = ']';
13756         *p++ = ' ';
13757     }
13758
13759     /* Active color */
13760     *p++ = whiteToPlay ? 'w' : 'b';
13761     *p++ = ' ';
13762
13763   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13764     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13765   } else {
13766   if(nrCastlingRights) {
13767      q = p;
13768      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13769        /* [HGM] write directly from rights */
13770            if(boards[move][CASTLING][2] != NoRights &&
13771               boards[move][CASTLING][0] != NoRights   )
13772                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13773            if(boards[move][CASTLING][2] != NoRights &&
13774               boards[move][CASTLING][1] != NoRights   )
13775                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13776            if(boards[move][CASTLING][5] != NoRights &&
13777               boards[move][CASTLING][3] != NoRights   )
13778                 *p++ = boards[move][CASTLING][3] + AAA;
13779            if(boards[move][CASTLING][5] != NoRights &&
13780               boards[move][CASTLING][4] != NoRights   )
13781                 *p++ = boards[move][CASTLING][4] + AAA;
13782      } else {
13783
13784         /* [HGM] write true castling rights */
13785         if( nrCastlingRights == 6 ) {
13786             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13787                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13788             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13789                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13790             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13791                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13792             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13793                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13794         }
13795      }
13796      if (q == p) *p++ = '-'; /* No castling rights */
13797      *p++ = ' ';
13798   }
13799
13800   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13801      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13802     /* En passant target square */
13803     if (move > backwardMostMove) {
13804         fromX = moveList[move - 1][0] - AAA;
13805         fromY = moveList[move - 1][1] - ONE;
13806         toX = moveList[move - 1][2] - AAA;
13807         toY = moveList[move - 1][3] - ONE;
13808         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13809             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13810             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13811             fromX == toX) {
13812             /* 2-square pawn move just happened */
13813             *p++ = toX + AAA;
13814             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13815         } else {
13816             *p++ = '-';
13817         }
13818     } else if(move == backwardMostMove) {
13819         // [HGM] perhaps we should always do it like this, and forget the above?
13820         if((signed char)boards[move][EP_STATUS] >= 0) {
13821             *p++ = boards[move][EP_STATUS] + AAA;
13822             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13823         } else {
13824             *p++ = '-';
13825         }
13826     } else {
13827         *p++ = '-';
13828     }
13829     *p++ = ' ';
13830   }
13831   }
13832
13833     /* [HGM] find reversible plies */
13834     {   int i = 0, j=move;
13835
13836         if (appData.debugMode) { int k;
13837             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13838             for(k=backwardMostMove; k<=forwardMostMove; k++)
13839                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13840
13841         }
13842
13843         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13844         if( j == backwardMostMove ) i += initialRulePlies;
13845         sprintf(p, "%d ", i);
13846         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13847     }
13848     /* Fullmove number */
13849     sprintf(p, "%d", (move / 2) + 1);
13850
13851     return StrSave(buf);
13852 }
13853
13854 Boolean
13855 ParseFEN(board, blackPlaysFirst, fen)
13856     Board board;
13857      int *blackPlaysFirst;
13858      char *fen;
13859 {
13860     int i, j;
13861     char *p;
13862     int emptycount;
13863     ChessSquare piece;
13864
13865     p = fen;
13866
13867     /* [HGM] by default clear Crazyhouse holdings, if present */
13868     if(gameInfo.holdingsWidth) {
13869        for(i=0; i<BOARD_HEIGHT; i++) {
13870            board[i][0]             = EmptySquare; /* black holdings */
13871            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13872            board[i][1]             = (ChessSquare) 0; /* black counts */
13873            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13874        }
13875     }
13876
13877     /* Piece placement data */
13878     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13879         j = 0;
13880         for (;;) {
13881             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13882                 if (*p == '/') p++;
13883                 emptycount = gameInfo.boardWidth - j;
13884                 while (emptycount--)
13885                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13886                 break;
13887 #if(BOARD_FILES >= 10)
13888             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13889                 p++; emptycount=10;
13890                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13891                 while (emptycount--)
13892                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13893 #endif
13894             } else if (isdigit(*p)) {
13895                 emptycount = *p++ - '0';
13896                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13897                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13898                 while (emptycount--)
13899                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13900             } else if (*p == '+' || isalpha(*p)) {
13901                 if (j >= gameInfo.boardWidth) return FALSE;
13902                 if(*p=='+') {
13903                     piece = CharToPiece(*++p);
13904                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13905                     piece = (ChessSquare) (PROMOTED piece ); p++;
13906                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13907                 } else piece = CharToPiece(*p++);
13908
13909                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13910                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13911                     piece = (ChessSquare) (PROMOTED piece);
13912                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13913                     p++;
13914                 }
13915                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13916             } else {
13917                 return FALSE;
13918             }
13919         }
13920     }
13921     while (*p == '/' || *p == ' ') p++;
13922
13923     /* [HGM] look for Crazyhouse holdings here */
13924     while(*p==' ') p++;
13925     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13926         if(*p == '[') p++;
13927         if(*p == '-' ) *p++; /* empty holdings */ else {
13928             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13929             /* if we would allow FEN reading to set board size, we would   */
13930             /* have to add holdings and shift the board read so far here   */
13931             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13932                 *p++;
13933                 if((int) piece >= (int) BlackPawn ) {
13934                     i = (int)piece - (int)BlackPawn;
13935                     i = PieceToNumber((ChessSquare)i);
13936                     if( i >= gameInfo.holdingsSize ) return FALSE;
13937                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13938                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13939                 } else {
13940                     i = (int)piece - (int)WhitePawn;
13941                     i = PieceToNumber((ChessSquare)i);
13942                     if( i >= gameInfo.holdingsSize ) return FALSE;
13943                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13944                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13945                 }
13946             }
13947         }
13948         if(*p == ']') *p++;
13949     }
13950
13951     while(*p == ' ') p++;
13952
13953     /* Active color */
13954     switch (*p++) {
13955       case 'w':
13956         *blackPlaysFirst = FALSE;
13957         break;
13958       case 'b':
13959         *blackPlaysFirst = TRUE;
13960         break;
13961       default:
13962         return FALSE;
13963     }
13964
13965     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13966     /* return the extra info in global variiables             */
13967
13968     /* set defaults in case FEN is incomplete */
13969     board[EP_STATUS] = EP_UNKNOWN;
13970     for(i=0; i<nrCastlingRights; i++ ) {
13971         board[CASTLING][i] =
13972             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13973     }   /* assume possible unless obviously impossible */
13974     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13975     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13976     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13977     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13978     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13979     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13980     FENrulePlies = 0;
13981
13982     while(*p==' ') p++;
13983     if(nrCastlingRights) {
13984       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13985           /* castling indicator present, so default becomes no castlings */
13986           for(i=0; i<nrCastlingRights; i++ ) {
13987                  board[CASTLING][i] = NoRights;
13988           }
13989       }
13990       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13991              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13992              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13993              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13994         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13995
13996         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13997             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13998             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13999         }
14000         switch(c) {
14001           case'K':
14002               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14003               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14004               board[CASTLING][2] = whiteKingFile;
14005               break;
14006           case'Q':
14007               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14008               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14009               board[CASTLING][2] = whiteKingFile;
14010               break;
14011           case'k':
14012               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14013               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14014               board[CASTLING][5] = blackKingFile;
14015               break;
14016           case'q':
14017               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14018               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14019               board[CASTLING][5] = blackKingFile;
14020           case '-':
14021               break;
14022           default: /* FRC castlings */
14023               if(c >= 'a') { /* black rights */
14024                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14025                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14026                   if(i == BOARD_RGHT) break;
14027                   board[CASTLING][5] = i;
14028                   c -= AAA;
14029                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14030                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14031                   if(c > i)
14032                       board[CASTLING][3] = c;
14033                   else
14034                       board[CASTLING][4] = c;
14035               } else { /* white rights */
14036                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14037                     if(board[0][i] == WhiteKing) break;
14038                   if(i == BOARD_RGHT) break;
14039                   board[CASTLING][2] = i;
14040                   c -= AAA - 'a' + 'A';
14041                   if(board[0][c] >= WhiteKing) break;
14042                   if(c > i)
14043                       board[CASTLING][0] = c;
14044                   else
14045                       board[CASTLING][1] = c;
14046               }
14047         }
14048       }
14049     if (appData.debugMode) {
14050         fprintf(debugFP, "FEN castling rights:");
14051         for(i=0; i<nrCastlingRights; i++)
14052         fprintf(debugFP, " %d", board[CASTLING][i]);
14053         fprintf(debugFP, "\n");
14054     }
14055
14056       while(*p==' ') p++;
14057     }
14058
14059     /* read e.p. field in games that know e.p. capture */
14060     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14061        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14062       if(*p=='-') {
14063         p++; board[EP_STATUS] = EP_NONE;
14064       } else {
14065          char c = *p++ - AAA;
14066
14067          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14068          if(*p >= '0' && *p <='9') *p++;
14069          board[EP_STATUS] = c;
14070       }
14071     }
14072
14073
14074     if(sscanf(p, "%d", &i) == 1) {
14075         FENrulePlies = i; /* 50-move ply counter */
14076         /* (The move number is still ignored)    */
14077     }
14078
14079     return TRUE;
14080 }
14081
14082 void
14083 EditPositionPasteFEN(char *fen)
14084 {
14085   if (fen != NULL) {
14086     Board initial_position;
14087
14088     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14089       DisplayError(_("Bad FEN position in clipboard"), 0);
14090       return ;
14091     } else {
14092       int savedBlackPlaysFirst = blackPlaysFirst;
14093       EditPositionEvent();
14094       blackPlaysFirst = savedBlackPlaysFirst;
14095       CopyBoard(boards[0], initial_position);
14096       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14097       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14098       DisplayBothClocks();
14099       DrawPosition(FALSE, boards[currentMove]);
14100     }
14101   }
14102 }
14103
14104 static char cseq[12] = "\\   ";
14105
14106 Boolean set_cont_sequence(char *new_seq)
14107 {
14108     int len;
14109     Boolean ret;
14110
14111     // handle bad attempts to set the sequence
14112         if (!new_seq)
14113                 return 0; // acceptable error - no debug
14114
14115     len = strlen(new_seq);
14116     ret = (len > 0) && (len < sizeof(cseq));
14117     if (ret)
14118         strcpy(cseq, new_seq);
14119     else if (appData.debugMode)
14120         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14121     return ret;
14122 }
14123
14124 /*
14125     reformat a source message so words don't cross the width boundary.  internal
14126     newlines are not removed.  returns the wrapped size (no null character unless
14127     included in source message).  If dest is NULL, only calculate the size required
14128     for the dest buffer.  lp argument indicats line position upon entry, and it's
14129     passed back upon exit.
14130 */
14131 int wrap(char *dest, char *src, int count, int width, int *lp)
14132 {
14133     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14134
14135     cseq_len = strlen(cseq);
14136     old_line = line = *lp;
14137     ansi = len = clen = 0;
14138
14139     for (i=0; i < count; i++)
14140     {
14141         if (src[i] == '\033')
14142             ansi = 1;
14143
14144         // if we hit the width, back up
14145         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14146         {
14147             // store i & len in case the word is too long
14148             old_i = i, old_len = len;
14149
14150             // find the end of the last word
14151             while (i && src[i] != ' ' && src[i] != '\n')
14152             {
14153                 i--;
14154                 len--;
14155             }
14156
14157             // word too long?  restore i & len before splitting it
14158             if ((old_i-i+clen) >= width)
14159             {
14160                 i = old_i;
14161                 len = old_len;
14162             }
14163
14164             // extra space?
14165             if (i && src[i-1] == ' ')
14166                 len--;
14167
14168             if (src[i] != ' ' && src[i] != '\n')
14169             {
14170                 i--;
14171                 if (len)
14172                     len--;
14173             }
14174
14175             // now append the newline and continuation sequence
14176             if (dest)
14177                 dest[len] = '\n';
14178             len++;
14179             if (dest)
14180                 strncpy(dest+len, cseq, cseq_len);
14181             len += cseq_len;
14182             line = cseq_len;
14183             clen = cseq_len;
14184             continue;
14185         }
14186
14187         if (dest)
14188             dest[len] = src[i];
14189         len++;
14190         if (!ansi)
14191             line++;
14192         if (src[i] == '\n')
14193             line = 0;
14194         if (src[i] == 'm')
14195             ansi = 0;
14196     }
14197     if (dest && appData.debugMode)
14198     {
14199         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14200             count, width, line, len, *lp);
14201         show_bytes(debugFP, src, count);
14202         fprintf(debugFP, "\ndest: ");
14203         show_bytes(debugFP, dest, len);
14204         fprintf(debugFP, "\n");
14205     }
14206     *lp = dest ? line : old_line;
14207
14208     return len;
14209 }
14210
14211 // [HGM] vari: routines for shelving variations
14212
14213 void 
14214 PushTail(int firstMove, int lastMove)
14215 {
14216         int i, j, nrMoves = lastMove - firstMove;
14217
14218         if(appData.icsActive) { // only in local mode
14219                 forwardMostMove = currentMove; // mimic old ICS behavior
14220                 return;
14221         }
14222         if(storedGames >= MAX_VARIATIONS-1) return;
14223
14224         // push current tail of game on stack
14225         savedResult[storedGames] = gameInfo.result;
14226         savedDetails[storedGames] = gameInfo.resultDetails;
14227         gameInfo.resultDetails = NULL;
14228         savedFirst[storedGames] = firstMove;
14229         savedLast [storedGames] = lastMove;
14230         savedFramePtr[storedGames] = framePtr;
14231         framePtr -= nrMoves; // reserve space for the boards
14232         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14233             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14234             for(j=0; j<MOVE_LEN; j++)
14235                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14236             for(j=0; j<2*MOVE_LEN; j++)
14237                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14238             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14239             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14240             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14241             pvInfoList[firstMove+i-1].depth = 0;
14242             commentList[framePtr+i] = commentList[firstMove+i];
14243             commentList[firstMove+i] = NULL;
14244         }
14245
14246         storedGames++;
14247         forwardMostMove = currentMove; // truncte game so we can start variation
14248         if(storedGames == 1) GreyRevert(FALSE);
14249 }
14250
14251 Boolean
14252 PopTail(Boolean annotate)
14253 {
14254         int i, j, nrMoves;
14255         char buf[8000], moveBuf[20];
14256
14257         if(appData.icsActive) return FALSE; // only in local mode
14258         if(!storedGames) return FALSE; // sanity
14259
14260         storedGames--;
14261         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14262         nrMoves = savedLast[storedGames] - currentMove;
14263         if(annotate) {
14264                 int cnt = 10;
14265                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14266                 else strcpy(buf, "(");
14267                 for(i=currentMove; i<forwardMostMove; i++) {
14268                         if(WhiteOnMove(i))
14269                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14270                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14271                         strcat(buf, moveBuf);
14272                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14273                 }
14274                 strcat(buf, ")");
14275         }
14276         for(i=1; i<nrMoves; i++) { // copy last variation back
14277             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14278             for(j=0; j<MOVE_LEN; j++)
14279                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14280             for(j=0; j<2*MOVE_LEN; j++)
14281                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14282             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14283             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14284             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14285             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14286             commentList[currentMove+i] = commentList[framePtr+i];
14287             commentList[framePtr+i] = NULL;
14288         }
14289         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14290         framePtr = savedFramePtr[storedGames];
14291         gameInfo.result = savedResult[storedGames];
14292         if(gameInfo.resultDetails != NULL) {
14293             free(gameInfo.resultDetails);
14294       }
14295         gameInfo.resultDetails = savedDetails[storedGames];
14296         forwardMostMove = currentMove + nrMoves;
14297         if(storedGames == 0) GreyRevert(TRUE);
14298         return TRUE;
14299 }
14300
14301 void 
14302 CleanupTail()
14303 {       // remove all shelved variations
14304         int i;
14305         for(i=0; i<storedGames; i++) {
14306             if(savedDetails[i])
14307                 free(savedDetails[i]);
14308             savedDetails[i] = NULL;
14309         }
14310         for(i=framePtr; i<MAX_MOVES; i++) {
14311                 if(commentList[i]) free(commentList[i]);
14312                 commentList[i] = NULL;
14313         }
14314         framePtr = MAX_MOVES-1;
14315         storedGames = 0;
14316 }