clean-up
[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    
1973    if (appData.debugMode) {
1974      fprintf(debugFP, "Switch board from %s to %s\n",
1975              VariantName(gameInfo.variant), VariantName(newVariant));
1976      setbuf(debugFP, NULL);
1977    }
1978    shuffleOpenings = 0;       /* [HGM] shuffle */
1979    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1980    switch(newVariant) 
1981      {
1982      case VariantShogi:
1983        newWidth = 9;  newHeight = 9;
1984        gameInfo.holdingsSize = 7;
1985      case VariantBughouse:
1986      case VariantCrazyhouse:
1987        newHoldingsWidth = 2; break;
1988      case VariantGreat:
1989        newWidth = 10;
1990      case VariantSuper:
1991        newHoldingsWidth = 2;
1992        gameInfo.holdingsSize = 8;
1993        break;
1994      case VariantGothic:
1995      case VariantCapablanca:
1996      case VariantCapaRandom:
1997        newWidth = 10;
1998      default:
1999        newHoldingsWidth = gameInfo.holdingsSize = 0;
2000      };
2001    
2002    if(newWidth  != gameInfo.boardWidth  ||
2003       newHeight != gameInfo.boardHeight ||
2004       newHoldingsWidth != gameInfo.holdingsWidth ) {
2005      
2006      /* shift position to new playing area, if needed */
2007      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2008        for(i=0; i<BOARD_HEIGHT; i++) 
2009          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2010            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011              board[i][j];
2012        for(i=0; i<newHeight; i++) {
2013          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2014          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2015        }
2016      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2017        for(i=0; i<BOARD_HEIGHT; i++)
2018          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2019            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2020              board[i][j];
2021      }
2022      gameInfo.boardWidth  = newWidth;
2023      gameInfo.boardHeight = newHeight;
2024      gameInfo.holdingsWidth = newHoldingsWidth;
2025      gameInfo.variant = newVariant;
2026      InitDrawingSizes(-2, 0);
2027    } else gameInfo.variant = newVariant;
2028    CopyBoard(oldBoard, board);   // remember correctly formatted board
2029      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2030    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2031 }
2032
2033 static int loggedOn = FALSE;
2034
2035 /*-- Game start info cache: --*/
2036 int gs_gamenum;
2037 char gs_kind[MSG_SIZ];
2038 static char player1Name[128] = "";
2039 static char player2Name[128] = "";
2040 static char cont_seq[] = "\n\\   ";
2041 static int player1Rating = -1;
2042 static int player2Rating = -1;
2043 /*----------------------------*/
2044
2045 ColorClass curColor = ColorNormal;
2046 int suppressKibitz = 0;
2047
2048 void
2049 read_from_ics(isr, closure, data, count, error)
2050      InputSourceRef isr;
2051      VOIDSTAR closure;
2052      char *data;
2053      int count;
2054      int error;
2055 {
2056 #define BUF_SIZE 8192
2057 #define STARTED_NONE 0
2058 #define STARTED_MOVES 1
2059 #define STARTED_BOARD 2
2060 #define STARTED_OBSERVE 3
2061 #define STARTED_HOLDINGS 4
2062 #define STARTED_CHATTER 5
2063 #define STARTED_COMMENT 6
2064 #define STARTED_MOVES_NOHIDE 7
2065     
2066     static int started = STARTED_NONE;
2067     static char parse[20000];
2068     static int parse_pos = 0;
2069     static char buf[BUF_SIZE + 1];
2070     static int firstTime = TRUE, intfSet = FALSE;
2071     static ColorClass prevColor = ColorNormal;
2072     static int savingComment = FALSE;
2073     static int cmatch = 0; // continuation sequence match
2074     char *bp;
2075     char str[500];
2076     int i, oldi;
2077     int buf_len;
2078     int next_out;
2079     int tkind;
2080     int backup;    /* [DM] For zippy color lines */
2081     char *p;
2082     char talker[MSG_SIZ]; // [HGM] chat
2083     int channel;
2084
2085     if (appData.debugMode) {
2086       if (!error) {
2087         fprintf(debugFP, "<ICS: ");
2088         show_bytes(debugFP, data, count);
2089         fprintf(debugFP, "\n");
2090       }
2091     }
2092
2093     if (appData.debugMode) { int f = forwardMostMove;
2094         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2095                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2096                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2097     }
2098     if (count > 0) {
2099         /* If last read ended with a partial line that we couldn't parse,
2100            prepend it to the new read and try again. */
2101         if (leftover_len > 0) {
2102             for (i=0; i<leftover_len; i++)
2103               buf[i] = buf[leftover_start + i];
2104         }
2105
2106     /* copy new characters into the buffer */
2107     bp = buf + leftover_len;
2108     buf_len=leftover_len;
2109     for (i=0; i<count; i++)
2110     {
2111         // ignore these
2112         if (data[i] == '\r')
2113             continue;
2114
2115         // join lines split by ICS?
2116         if (!appData.noJoin)
2117         {
2118             /*
2119                 Joining just consists of finding matches against the
2120                 continuation sequence, and discarding that sequence
2121                 if found instead of copying it.  So, until a match
2122                 fails, there's nothing to do since it might be the
2123                 complete sequence, and thus, something we don't want
2124                 copied.
2125             */
2126             if (data[i] == cont_seq[cmatch])
2127             {
2128                 cmatch++;
2129                 if (cmatch == strlen(cont_seq))
2130                 {
2131                     cmatch = 0; // complete match.  just reset the counter
2132
2133                     /*
2134                         it's possible for the ICS to not include the space
2135                         at the end of the last word, making our [correct]
2136                         join operation fuse two separate words.  the server
2137                         does this when the space occurs at the width setting.
2138                     */
2139                     if (!buf_len || buf[buf_len-1] != ' ')
2140                     {
2141                         *bp++ = ' ';
2142                         buf_len++;
2143                     }
2144                 }
2145                 continue;
2146             }
2147             else if (cmatch)
2148             {
2149                 /*
2150                     match failed, so we have to copy what matched before
2151                     falling through and copying this character.  In reality,
2152                     this will only ever be just the newline character, but
2153                     it doesn't hurt to be precise.
2154                 */
2155                 strncpy(bp, cont_seq, cmatch);
2156                 bp += cmatch;
2157                 buf_len += cmatch;
2158                 cmatch = 0;
2159             }
2160         }
2161
2162         // copy this char
2163         *bp++ = data[i];
2164         buf_len++;
2165     }
2166
2167         buf[buf_len] = NULLCHAR;
2168         next_out = leftover_len;
2169         leftover_start = 0;
2170         
2171         i = 0;
2172         while (i < buf_len) {
2173             /* Deal with part of the TELNET option negotiation
2174                protocol.  We refuse to do anything beyond the
2175                defaults, except that we allow the WILL ECHO option,
2176                which ICS uses to turn off password echoing when we are
2177                directly connected to it.  We reject this option
2178                if localLineEditing mode is on (always on in xboard)
2179                and we are talking to port 23, which might be a real
2180                telnet server that will try to keep WILL ECHO on permanently.
2181              */
2182             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2183                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2184                 unsigned char option;
2185                 oldi = i;
2186                 switch ((unsigned char) buf[++i]) {
2187                   case TN_WILL:
2188                     if (appData.debugMode)
2189                       fprintf(debugFP, "\n<WILL ");
2190                     switch (option = (unsigned char) buf[++i]) {
2191                       case TN_ECHO:
2192                         if (appData.debugMode)
2193                           fprintf(debugFP, "ECHO ");
2194                         /* Reply only if this is a change, according
2195                            to the protocol rules. */
2196                         if (remoteEchoOption) break;
2197                         if (appData.localLineEditing &&
2198                             atoi(appData.icsPort) == TN_PORT) {
2199                             TelnetRequest(TN_DONT, TN_ECHO);
2200                         } else {
2201                             EchoOff();
2202                             TelnetRequest(TN_DO, TN_ECHO);
2203                             remoteEchoOption = TRUE;
2204                         }
2205                         break;
2206                       default:
2207                         if (appData.debugMode)
2208                           fprintf(debugFP, "%d ", option);
2209                         /* Whatever this is, we don't want it. */
2210                         TelnetRequest(TN_DONT, option);
2211                         break;
2212                     }
2213                     break;
2214                   case TN_WONT:
2215                     if (appData.debugMode)
2216                       fprintf(debugFP, "\n<WONT ");
2217                     switch (option = (unsigned char) buf[++i]) {
2218                       case TN_ECHO:
2219                         if (appData.debugMode)
2220                           fprintf(debugFP, "ECHO ");
2221                         /* Reply only if this is a change, according
2222                            to the protocol rules. */
2223                         if (!remoteEchoOption) break;
2224                         EchoOn();
2225                         TelnetRequest(TN_DONT, TN_ECHO);
2226                         remoteEchoOption = FALSE;
2227                         break;
2228                       default:
2229                         if (appData.debugMode)
2230                           fprintf(debugFP, "%d ", (unsigned char) option);
2231                         /* Whatever this is, it must already be turned
2232                            off, because we never agree to turn on
2233                            anything non-default, so according to the
2234                            protocol rules, we don't reply. */
2235                         break;
2236                     }
2237                     break;
2238                   case TN_DO:
2239                     if (appData.debugMode)
2240                       fprintf(debugFP, "\n<DO ");
2241                     switch (option = (unsigned char) buf[++i]) {
2242                       default:
2243                         /* Whatever this is, we refuse to do it. */
2244                         if (appData.debugMode)
2245                           fprintf(debugFP, "%d ", option);
2246                         TelnetRequest(TN_WONT, option);
2247                         break;
2248                     }
2249                     break;
2250                   case TN_DONT:
2251                     if (appData.debugMode)
2252                       fprintf(debugFP, "\n<DONT ");
2253                     switch (option = (unsigned char) buf[++i]) {
2254                       default:
2255                         if (appData.debugMode)
2256                           fprintf(debugFP, "%d ", option);
2257                         /* Whatever this is, we are already not doing
2258                            it, because we never agree to do anything
2259                            non-default, so according to the protocol
2260                            rules, we don't reply. */
2261                         break;
2262                     }
2263                     break;
2264                   case TN_IAC:
2265                     if (appData.debugMode)
2266                       fprintf(debugFP, "\n<IAC ");
2267                     /* Doubled IAC; pass it through */
2268                     i--;
2269                     break;
2270                   default:
2271                     if (appData.debugMode)
2272                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2273                     /* Drop all other telnet commands on the floor */
2274                     break;
2275                 }
2276                 if (oldi > next_out)
2277                   SendToPlayer(&buf[next_out], oldi - next_out);
2278                 if (++i > next_out)
2279                   next_out = i;
2280                 continue;
2281             }
2282                 
2283             /* OK, this at least will *usually* work */
2284             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2285                 loggedOn = TRUE;
2286             }
2287             
2288             if (loggedOn && !intfSet) {
2289                 if (ics_type == ICS_ICC) {
2290                   sprintf(str,
2291                           "/set-quietly interface %s\n/set-quietly style 12\n",
2292                           programVersion);
2293                 } else if (ics_type == ICS_CHESSNET) {
2294                   sprintf(str, "/style 12\n");
2295                 } else {
2296                   strcpy(str, "alias $ @\n$set interface ");
2297                   strcat(str, programVersion);
2298                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2299 #ifdef WIN32
2300                   strcat(str, "$iset nohighlight 1\n");
2301 #endif
2302                   strcat(str, "$iset lock 1\n$style 12\n");
2303                 }
2304                 SendToICS(str);
2305                 NotifyFrontendLogin();
2306                 intfSet = TRUE;
2307             }
2308
2309             if (started == STARTED_COMMENT) {
2310                 /* Accumulate characters in comment */
2311                 parse[parse_pos++] = buf[i];
2312                 if (buf[i] == '\n') {
2313                     parse[parse_pos] = NULLCHAR;
2314                     if(chattingPartner>=0) {
2315                         char mess[MSG_SIZ];
2316                         sprintf(mess, "%s%s", talker, parse);
2317                         OutputChatMessage(chattingPartner, mess);
2318                         chattingPartner = -1;
2319                     } else
2320                     if(!suppressKibitz) // [HGM] kibitz
2321                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2322                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2323                         int nrDigit = 0, nrAlph = 0, i;
2324                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2325                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2326                         parse[parse_pos] = NULLCHAR;
2327                         // try to be smart: if it does not look like search info, it should go to
2328                         // ICS interaction window after all, not to engine-output window.
2329                         for(i=0; i<parse_pos; i++) { // count letters and digits
2330                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2331                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2332                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2333                         }
2334                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2335                             int depth=0; float score;
2336                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2337                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2338                                 pvInfoList[forwardMostMove-1].depth = depth;
2339                                 pvInfoList[forwardMostMove-1].score = 100*score;
2340                             }
2341                             OutputKibitz(suppressKibitz, parse);
2342                         } else {
2343                             char tmp[MSG_SIZ];
2344                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2345                             SendToPlayer(tmp, strlen(tmp));
2346                         }
2347                     }
2348                     started = STARTED_NONE;
2349                 } else {
2350                     /* Don't match patterns against characters in chatter */
2351                     i++;
2352                     continue;
2353                 }
2354             }
2355             if (started == STARTED_CHATTER) {
2356                 if (buf[i] != '\n') {
2357                     /* Don't match patterns against characters in chatter */
2358                     i++;
2359                     continue;
2360                 }
2361                 started = STARTED_NONE;
2362             }
2363
2364             /* Kludge to deal with rcmd protocol */
2365             if (firstTime && looking_at(buf, &i, "\001*")) {
2366                 DisplayFatalError(&buf[1], 0, 1);
2367                 continue;
2368             } else {
2369                 firstTime = FALSE;
2370             }
2371
2372             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2373                 ics_type = ICS_ICC;
2374                 ics_prefix = "/";
2375                 if (appData.debugMode)
2376                   fprintf(debugFP, "ics_type %d\n", ics_type);
2377                 continue;
2378             }
2379             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2380                 ics_type = ICS_FICS;
2381                 ics_prefix = "$";
2382                 if (appData.debugMode)
2383                   fprintf(debugFP, "ics_type %d\n", ics_type);
2384                 continue;
2385             }
2386             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2387                 ics_type = ICS_CHESSNET;
2388                 ics_prefix = "/";
2389                 if (appData.debugMode)
2390                   fprintf(debugFP, "ics_type %d\n", ics_type);
2391                 continue;
2392             }
2393
2394             if (!loggedOn &&
2395                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2396                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2397                  looking_at(buf, &i, "will be \"*\""))) {
2398               strcpy(ics_handle, star_match[0]);
2399               continue;
2400             }
2401
2402             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2403               char buf[MSG_SIZ];
2404               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2405               DisplayIcsInteractionTitle(buf);
2406               have_set_title = TRUE;
2407             }
2408
2409             /* skip finger notes */
2410             if (started == STARTED_NONE &&
2411                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2412                  (buf[i] == '1' && buf[i+1] == '0')) &&
2413                 buf[i+2] == ':' && buf[i+3] == ' ') {
2414               started = STARTED_CHATTER;
2415               i += 3;
2416               continue;
2417             }
2418
2419             /* skip formula vars */
2420             if (started == STARTED_NONE &&
2421                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2422               started = STARTED_CHATTER;
2423               i += 3;
2424               continue;
2425             }
2426
2427             oldi = i;
2428             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2429             if (appData.autoKibitz && started == STARTED_NONE && 
2430                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2431                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2432                 if(looking_at(buf, &i, "* kibitzes: ") &&
2433                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2434                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2435                         suppressKibitz = TRUE;
2436                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2437                                 && (gameMode == IcsPlayingWhite)) ||
2438                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2439                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2440                             started = STARTED_CHATTER; // own kibitz we simply discard
2441                         else {
2442                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2443                             parse_pos = 0; parse[0] = NULLCHAR;
2444                             savingComment = TRUE;
2445                             suppressKibitz = gameMode != IcsObserving ? 2 :
2446                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2447                         } 
2448                         continue;
2449                 } else
2450                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2451                     started = STARTED_CHATTER;
2452                     suppressKibitz = TRUE;
2453                 }
2454             } // [HGM] kibitz: end of patch
2455
2456 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2457
2458             // [HGM] chat: intercept tells by users for which we have an open chat window
2459             channel = -1;
2460             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2461                                            looking_at(buf, &i, "* whispers:") ||
2462                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2463                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2464                 int p;
2465                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2466                 chattingPartner = -1;
2467
2468                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2469                 for(p=0; p<MAX_CHAT; p++) {
2470                     if(channel == atoi(chatPartner[p])) {
2471                     talker[0] = '['; strcat(talker, "]");
2472                     chattingPartner = p; break;
2473                     }
2474                 } else
2475                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2476                 for(p=0; p<MAX_CHAT; p++) {
2477                     if(!strcmp("WHISPER", chatPartner[p])) {
2478                         talker[0] = '['; strcat(talker, "]");
2479                         chattingPartner = p; break;
2480                     }
2481                 }
2482                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2483                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2484                     talker[0] = 0;
2485                     chattingPartner = p; break;
2486                 }
2487                 if(chattingPartner<0) i = oldi; else {
2488                     started = STARTED_COMMENT;
2489                     parse_pos = 0; parse[0] = NULLCHAR;
2490                     savingComment = TRUE;
2491                     suppressKibitz = TRUE;
2492                 }
2493             } // [HGM] chat: end of patch
2494
2495             if (appData.zippyTalk || appData.zippyPlay) {
2496                 /* [DM] Backup address for color zippy lines */
2497                 backup = i;
2498 #if ZIPPY
2499        #ifdef WIN32
2500                if (loggedOn == TRUE)
2501                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2502                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2503        #else
2504                 if (ZippyControl(buf, &i) ||
2505                     ZippyConverse(buf, &i) ||
2506                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2507                       loggedOn = TRUE;
2508                       if (!appData.colorize) continue;
2509                 }
2510        #endif
2511 #endif
2512             } // [DM] 'else { ' deleted
2513                 if (
2514                     /* Regular tells and says */
2515                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2516                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2517                     looking_at(buf, &i, "* says: ") ||
2518                     /* Don't color "message" or "messages" output */
2519                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2520                     looking_at(buf, &i, "*. * at *:*: ") ||
2521                     looking_at(buf, &i, "--* (*:*): ") ||
2522                     /* Message notifications (same color as tells) */
2523                     looking_at(buf, &i, "* has left a message ") ||
2524                     looking_at(buf, &i, "* just sent you a message:\n") ||
2525                     /* Whispers and kibitzes */
2526                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2527                     looking_at(buf, &i, "* kibitzes: ") ||
2528                     /* Channel tells */
2529                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2530
2531                   if (tkind == 1 && strchr(star_match[0], ':')) {
2532                       /* Avoid "tells you:" spoofs in channels */
2533                      tkind = 3;
2534                   }
2535                   if (star_match[0][0] == NULLCHAR ||
2536                       strchr(star_match[0], ' ') ||
2537                       (tkind == 3 && strchr(star_match[1], ' '))) {
2538                     /* Reject bogus matches */
2539                     i = oldi;
2540                   } else {
2541                     if (appData.colorize) {
2542                       if (oldi > next_out) {
2543                         SendToPlayer(&buf[next_out], oldi - next_out);
2544                         next_out = oldi;
2545                       }
2546                       switch (tkind) {
2547                       case 1:
2548                         Colorize(ColorTell, FALSE);
2549                         curColor = ColorTell;
2550                         break;
2551                       case 2:
2552                         Colorize(ColorKibitz, FALSE);
2553                         curColor = ColorKibitz;
2554                         break;
2555                       case 3:
2556                         p = strrchr(star_match[1], '(');
2557                         if (p == NULL) {
2558                           p = star_match[1];
2559                         } else {
2560                           p++;
2561                         }
2562                         if (atoi(p) == 1) {
2563                           Colorize(ColorChannel1, FALSE);
2564                           curColor = ColorChannel1;
2565                         } else {
2566                           Colorize(ColorChannel, FALSE);
2567                           curColor = ColorChannel;
2568                         }
2569                         break;
2570                       case 5:
2571                         curColor = ColorNormal;
2572                         break;
2573                       }
2574                     }
2575                     if (started == STARTED_NONE && appData.autoComment &&
2576                         (gameMode == IcsObserving ||
2577                          gameMode == IcsPlayingWhite ||
2578                          gameMode == IcsPlayingBlack)) {
2579                       parse_pos = i - oldi;
2580                       memcpy(parse, &buf[oldi], parse_pos);
2581                       parse[parse_pos] = NULLCHAR;
2582                       started = STARTED_COMMENT;
2583                       savingComment = TRUE;
2584                     } else {
2585                       started = STARTED_CHATTER;
2586                       savingComment = FALSE;
2587                     }
2588                     loggedOn = TRUE;
2589                     continue;
2590                   }
2591                 }
2592
2593                 if (looking_at(buf, &i, "* s-shouts: ") ||
2594                     looking_at(buf, &i, "* c-shouts: ")) {
2595                     if (appData.colorize) {
2596                         if (oldi > next_out) {
2597                             SendToPlayer(&buf[next_out], oldi - next_out);
2598                             next_out = oldi;
2599                         }
2600                         Colorize(ColorSShout, FALSE);
2601                         curColor = ColorSShout;
2602                     }
2603                     loggedOn = TRUE;
2604                     started = STARTED_CHATTER;
2605                     continue;
2606                 }
2607
2608                 if (looking_at(buf, &i, "--->")) {
2609                     loggedOn = TRUE;
2610                     continue;
2611                 }
2612
2613                 if (looking_at(buf, &i, "* shouts: ") ||
2614                     looking_at(buf, &i, "--> ")) {
2615                     if (appData.colorize) {
2616                         if (oldi > next_out) {
2617                             SendToPlayer(&buf[next_out], oldi - next_out);
2618                             next_out = oldi;
2619                         }
2620                         Colorize(ColorShout, FALSE);
2621                         curColor = ColorShout;
2622                     }
2623                     loggedOn = TRUE;
2624                     started = STARTED_CHATTER;
2625                     continue;
2626                 }
2627
2628                 if (looking_at( buf, &i, "Challenge:")) {
2629                     if (appData.colorize) {
2630                         if (oldi > next_out) {
2631                             SendToPlayer(&buf[next_out], oldi - next_out);
2632                             next_out = oldi;
2633                         }
2634                         Colorize(ColorChallenge, FALSE);
2635                         curColor = ColorChallenge;
2636                     }
2637                     loggedOn = TRUE;
2638                     continue;
2639                 }
2640
2641                 if (looking_at(buf, &i, "* offers you") ||
2642                     looking_at(buf, &i, "* offers to be") ||
2643                     looking_at(buf, &i, "* would like to") ||
2644                     looking_at(buf, &i, "* requests to") ||
2645                     looking_at(buf, &i, "Your opponent offers") ||
2646                     looking_at(buf, &i, "Your opponent requests")) {
2647
2648                     if (appData.colorize) {
2649                         if (oldi > next_out) {
2650                             SendToPlayer(&buf[next_out], oldi - next_out);
2651                             next_out = oldi;
2652                         }
2653                         Colorize(ColorRequest, FALSE);
2654                         curColor = ColorRequest;
2655                     }
2656                     continue;
2657                 }
2658
2659                 if (looking_at(buf, &i, "* (*) seeking")) {
2660                     if (appData.colorize) {
2661                         if (oldi > next_out) {
2662                             SendToPlayer(&buf[next_out], oldi - next_out);
2663                             next_out = oldi;
2664                         }
2665                         Colorize(ColorSeek, FALSE);
2666                         curColor = ColorSeek;
2667                     }
2668                     continue;
2669             }
2670
2671             if (looking_at(buf, &i, "\\   ")) {
2672                 if (prevColor != ColorNormal) {
2673                     if (oldi > next_out) {
2674                         SendToPlayer(&buf[next_out], oldi - next_out);
2675                         next_out = oldi;
2676                     }
2677                     Colorize(prevColor, TRUE);
2678                     curColor = prevColor;
2679                 }
2680                 if (savingComment) {
2681                     parse_pos = i - oldi;
2682                     memcpy(parse, &buf[oldi], parse_pos);
2683                     parse[parse_pos] = NULLCHAR;
2684                     started = STARTED_COMMENT;
2685                 } else {
2686                     started = STARTED_CHATTER;
2687                 }
2688                 continue;
2689             }
2690
2691             if (looking_at(buf, &i, "Black Strength :") ||
2692                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2693                 looking_at(buf, &i, "<10>") ||
2694                 looking_at(buf, &i, "#@#")) {
2695                 /* Wrong board style */
2696                 loggedOn = TRUE;
2697                 SendToICS(ics_prefix);
2698                 SendToICS("set style 12\n");
2699                 SendToICS(ics_prefix);
2700                 SendToICS("refresh\n");
2701                 continue;
2702             }
2703             
2704             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2705                 ICSInitScript();
2706                 have_sent_ICS_logon = 1;
2707                 continue;
2708             }
2709               
2710             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2711                 (looking_at(buf, &i, "\n<12> ") ||
2712                  looking_at(buf, &i, "<12> "))) {
2713                 loggedOn = TRUE;
2714                 if (oldi > next_out) {
2715                     SendToPlayer(&buf[next_out], oldi - next_out);
2716                 }
2717                 next_out = i;
2718                 started = STARTED_BOARD;
2719                 parse_pos = 0;
2720                 continue;
2721             }
2722
2723             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2724                 looking_at(buf, &i, "<b1> ")) {
2725                 if (oldi > next_out) {
2726                     SendToPlayer(&buf[next_out], oldi - next_out);
2727                 }
2728                 next_out = i;
2729                 started = STARTED_HOLDINGS;
2730                 parse_pos = 0;
2731                 continue;
2732             }
2733
2734             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2735                 loggedOn = TRUE;
2736                 /* Header for a move list -- first line */
2737
2738                 switch (ics_getting_history) {
2739                   case H_FALSE:
2740                     switch (gameMode) {
2741                       case IcsIdle:
2742                       case BeginningOfGame:
2743                         /* User typed "moves" or "oldmoves" while we
2744                            were idle.  Pretend we asked for these
2745                            moves and soak them up so user can step
2746                            through them and/or save them.
2747                            */
2748                         Reset(FALSE, TRUE);
2749                         gameMode = IcsObserving;
2750                         ModeHighlight();
2751                         ics_gamenum = -1;
2752                         ics_getting_history = H_GOT_UNREQ_HEADER;
2753                         break;
2754                       case EditGame: /*?*/
2755                       case EditPosition: /*?*/
2756                         /* Should above feature work in these modes too? */
2757                         /* For now it doesn't */
2758                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2759                         break;
2760                       default:
2761                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2762                         break;
2763                     }
2764                     break;
2765                   case H_REQUESTED:
2766                     /* Is this the right one? */
2767                     if (gameInfo.white && gameInfo.black &&
2768                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2769                         strcmp(gameInfo.black, star_match[2]) == 0) {
2770                         /* All is well */
2771                         ics_getting_history = H_GOT_REQ_HEADER;
2772                     }
2773                     break;
2774                   case H_GOT_REQ_HEADER:
2775                   case H_GOT_UNREQ_HEADER:
2776                   case H_GOT_UNWANTED_HEADER:
2777                   case H_GETTING_MOVES:
2778                     /* Should not happen */
2779                     DisplayError(_("Error gathering move list: two headers"), 0);
2780                     ics_getting_history = H_FALSE;
2781                     break;
2782                 }
2783
2784                 /* Save player ratings into gameInfo if needed */
2785                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2786                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2787                     (gameInfo.whiteRating == -1 ||
2788                      gameInfo.blackRating == -1)) {
2789
2790                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2791                     gameInfo.blackRating = string_to_rating(star_match[3]);
2792                     if (appData.debugMode)
2793                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2794                               gameInfo.whiteRating, gameInfo.blackRating);
2795                 }
2796                 continue;
2797             }
2798
2799             if (looking_at(buf, &i,
2800               "* * match, initial time: * minute*, increment: * second")) {
2801                 /* Header for a move list -- second line */
2802                 /* Initial board will follow if this is a wild game */
2803                 if (gameInfo.event != NULL) free(gameInfo.event);
2804                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2805                 gameInfo.event = StrSave(str);
2806                 /* [HGM] we switched variant. Translate boards if needed. */
2807                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2808                 continue;
2809             }
2810
2811             if (looking_at(buf, &i, "Move  ")) {
2812                 /* Beginning of a move list */
2813                 switch (ics_getting_history) {
2814                   case H_FALSE:
2815                     /* Normally should not happen */
2816                     /* Maybe user hit reset while we were parsing */
2817                     break;
2818                   case H_REQUESTED:
2819                     /* Happens if we are ignoring a move list that is not
2820                      * the one we just requested.  Common if the user
2821                      * tries to observe two games without turning off
2822                      * getMoveList */
2823                     break;
2824                   case H_GETTING_MOVES:
2825                     /* Should not happen */
2826                     DisplayError(_("Error gathering move list: nested"), 0);
2827                     ics_getting_history = H_FALSE;
2828                     break;
2829                   case H_GOT_REQ_HEADER:
2830                     ics_getting_history = H_GETTING_MOVES;
2831                     started = STARTED_MOVES;
2832                     parse_pos = 0;
2833                     if (oldi > next_out) {
2834                         SendToPlayer(&buf[next_out], oldi - next_out);
2835                     }
2836                     break;
2837                   case H_GOT_UNREQ_HEADER:
2838                     ics_getting_history = H_GETTING_MOVES;
2839                     started = STARTED_MOVES_NOHIDE;
2840                     parse_pos = 0;
2841                     break;
2842                   case H_GOT_UNWANTED_HEADER:
2843                     ics_getting_history = H_FALSE;
2844                     break;
2845                 }
2846                 continue;
2847             }                           
2848             
2849             if (looking_at(buf, &i, "% ") ||
2850                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2851                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2852                 savingComment = FALSE;
2853                 switch (started) {
2854                   case STARTED_MOVES:
2855                   case STARTED_MOVES_NOHIDE:
2856                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2857                     parse[parse_pos + i - oldi] = NULLCHAR;
2858                     ParseGameHistory(parse);
2859 #if ZIPPY
2860                     if (appData.zippyPlay && first.initDone) {
2861                         FeedMovesToProgram(&first, forwardMostMove);
2862                         if (gameMode == IcsPlayingWhite) {
2863                             if (WhiteOnMove(forwardMostMove)) {
2864                                 if (first.sendTime) {
2865                                   if (first.useColors) {
2866                                     SendToProgram("black\n", &first); 
2867                                   }
2868                                   SendTimeRemaining(&first, TRUE);
2869                                 }
2870                                 if (first.useColors) {
2871                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2872                                 }
2873                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2874                                 first.maybeThinking = TRUE;
2875                             } else {
2876                                 if (first.usePlayother) {
2877                                   if (first.sendTime) {
2878                                     SendTimeRemaining(&first, TRUE);
2879                                   }
2880                                   SendToProgram("playother\n", &first);
2881                                   firstMove = FALSE;
2882                                 } else {
2883                                   firstMove = TRUE;
2884                                 }
2885                             }
2886                         } else if (gameMode == IcsPlayingBlack) {
2887                             if (!WhiteOnMove(forwardMostMove)) {
2888                                 if (first.sendTime) {
2889                                   if (first.useColors) {
2890                                     SendToProgram("white\n", &first);
2891                                   }
2892                                   SendTimeRemaining(&first, FALSE);
2893                                 }
2894                                 if (first.useColors) {
2895                                   SendToProgram("black\n", &first);
2896                                 }
2897                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2898                                 first.maybeThinking = TRUE;
2899                             } else {
2900                                 if (first.usePlayother) {
2901                                   if (first.sendTime) {
2902                                     SendTimeRemaining(&first, FALSE);
2903                                   }
2904                                   SendToProgram("playother\n", &first);
2905                                   firstMove = FALSE;
2906                                 } else {
2907                                   firstMove = TRUE;
2908                                 }
2909                             }
2910                         }                       
2911                     }
2912 #endif
2913                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2914                         /* Moves came from oldmoves or moves command
2915                            while we weren't doing anything else.
2916                            */
2917                         currentMove = forwardMostMove;
2918                         ClearHighlights();/*!!could figure this out*/
2919                         flipView = appData.flipView;
2920                         DrawPosition(TRUE, boards[currentMove]);
2921                         DisplayBothClocks();
2922                         sprintf(str, "%s vs. %s",
2923                                 gameInfo.white, gameInfo.black);
2924                         DisplayTitle(str);
2925                         gameMode = IcsIdle;
2926                     } else {
2927                         /* Moves were history of an active game */
2928                         if (gameInfo.resultDetails != NULL) {
2929                             free(gameInfo.resultDetails);
2930                             gameInfo.resultDetails = NULL;
2931                         }
2932                     }
2933                     HistorySet(parseList, backwardMostMove,
2934                                forwardMostMove, currentMove-1);
2935                     DisplayMove(currentMove - 1);
2936                     if (started == STARTED_MOVES) next_out = i;
2937                     started = STARTED_NONE;
2938                     ics_getting_history = H_FALSE;
2939                     break;
2940
2941                   case STARTED_OBSERVE:
2942                     started = STARTED_NONE;
2943                     SendToICS(ics_prefix);
2944                     SendToICS("refresh\n");
2945                     break;
2946
2947                   default:
2948                     break;
2949                 }
2950                 if(bookHit) { // [HGM] book: simulate book reply
2951                     static char bookMove[MSG_SIZ]; // a bit generous?
2952
2953                     programStats.nodes = programStats.depth = programStats.time = 
2954                     programStats.score = programStats.got_only_move = 0;
2955                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2956
2957                     strcpy(bookMove, "move ");
2958                     strcat(bookMove, bookHit);
2959                     HandleMachineMove(bookMove, &first);
2960                 }
2961                 continue;
2962             }
2963             
2964             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2965                  started == STARTED_HOLDINGS ||
2966                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2967                 /* Accumulate characters in move list or board */
2968                 parse[parse_pos++] = buf[i];
2969             }
2970             
2971             /* Start of game messages.  Mostly we detect start of game
2972                when the first board image arrives.  On some versions
2973                of the ICS, though, we need to do a "refresh" after starting
2974                to observe in order to get the current board right away. */
2975             if (looking_at(buf, &i, "Adding game * to observation list")) {
2976                 started = STARTED_OBSERVE;
2977                 continue;
2978             }
2979
2980             /* Handle auto-observe */
2981             if (appData.autoObserve &&
2982                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2983                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2984                 char *player;
2985                 /* Choose the player that was highlighted, if any. */
2986                 if (star_match[0][0] == '\033' ||
2987                     star_match[1][0] != '\033') {
2988                     player = star_match[0];
2989                 } else {
2990                     player = star_match[2];
2991                 }
2992                 sprintf(str, "%sobserve %s\n",
2993                         ics_prefix, StripHighlightAndTitle(player));
2994                 SendToICS(str);
2995
2996                 /* Save ratings from notify string */
2997                 strcpy(player1Name, star_match[0]);
2998                 player1Rating = string_to_rating(star_match[1]);
2999                 strcpy(player2Name, star_match[2]);
3000                 player2Rating = string_to_rating(star_match[3]);
3001
3002                 if (appData.debugMode)
3003                   fprintf(debugFP, 
3004                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3005                           player1Name, player1Rating,
3006                           player2Name, player2Rating);
3007
3008                 continue;
3009             }
3010
3011             /* Deal with automatic examine mode after a game,
3012                and with IcsObserving -> IcsExamining transition */
3013             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3014                 looking_at(buf, &i, "has made you an examiner of game *")) {
3015
3016                 int gamenum = atoi(star_match[0]);
3017                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3018                     gamenum == ics_gamenum) {
3019                     /* We were already playing or observing this game;
3020                        no need to refetch history */
3021                     gameMode = IcsExamining;
3022                     if (pausing) {
3023                         pauseExamForwardMostMove = forwardMostMove;
3024                     } else if (currentMove < forwardMostMove) {
3025                         ForwardInner(forwardMostMove);
3026                     }
3027                 } else {
3028                     /* I don't think this case really can happen */
3029                     SendToICS(ics_prefix);
3030                     SendToICS("refresh\n");
3031                 }
3032                 continue;
3033             }    
3034             
3035             /* Error messages */
3036 //          if (ics_user_moved) {
3037             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3038                 if (looking_at(buf, &i, "Illegal move") ||
3039                     looking_at(buf, &i, "Not a legal move") ||
3040                     looking_at(buf, &i, "Your king is in check") ||
3041                     looking_at(buf, &i, "It isn't your turn") ||
3042                     looking_at(buf, &i, "It is not your move")) {
3043                     /* Illegal move */
3044                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3045                         currentMove = --forwardMostMove;
3046                         DisplayMove(currentMove - 1); /* before DMError */
3047                         DrawPosition(FALSE, boards[currentMove]);
3048                         SwitchClocks();
3049                         DisplayBothClocks();
3050                     }
3051                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3052                     ics_user_moved = 0;
3053                     continue;
3054                 }
3055             }
3056
3057             if (looking_at(buf, &i, "still have time") ||
3058                 looking_at(buf, &i, "not out of time") ||
3059                 looking_at(buf, &i, "either player is out of time") ||
3060                 looking_at(buf, &i, "has timeseal; checking")) {
3061                 /* We must have called his flag a little too soon */
3062                 whiteFlag = blackFlag = FALSE;
3063                 continue;
3064             }
3065
3066             if (looking_at(buf, &i, "added * seconds to") ||
3067                 looking_at(buf, &i, "seconds were added to")) {
3068                 /* Update the clocks */
3069                 SendToICS(ics_prefix);
3070                 SendToICS("refresh\n");
3071                 continue;
3072             }
3073
3074             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3075                 ics_clock_paused = TRUE;
3076                 StopClocks();
3077                 continue;
3078             }
3079
3080             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3081                 ics_clock_paused = FALSE;
3082                 StartClocks();
3083                 continue;
3084             }
3085
3086             /* Grab player ratings from the Creating: message.
3087                Note we have to check for the special case when
3088                the ICS inserts things like [white] or [black]. */
3089             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3090                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3091                 /* star_matches:
3092                    0    player 1 name (not necessarily white)
3093                    1    player 1 rating
3094                    2    empty, white, or black (IGNORED)
3095                    3    player 2 name (not necessarily black)
3096                    4    player 2 rating
3097                    
3098                    The names/ratings are sorted out when the game
3099                    actually starts (below).
3100                 */
3101                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3102                 player1Rating = string_to_rating(star_match[1]);
3103                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3104                 player2Rating = string_to_rating(star_match[4]);
3105
3106                 if (appData.debugMode)
3107                   fprintf(debugFP, 
3108                           "Ratings from 'Creating:' %s %d, %s %d\n",
3109                           player1Name, player1Rating,
3110                           player2Name, player2Rating);
3111
3112                 continue;
3113             }
3114             
3115             /* Improved generic start/end-of-game messages */
3116             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3117                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3118                 /* If tkind == 0: */
3119                 /* star_match[0] is the game number */
3120                 /*           [1] is the white player's name */
3121                 /*           [2] is the black player's name */
3122                 /* For end-of-game: */
3123                 /*           [3] is the reason for the game end */
3124                 /*           [4] is a PGN end game-token, preceded by " " */
3125                 /* For start-of-game: */
3126                 /*           [3] begins with "Creating" or "Continuing" */
3127                 /*           [4] is " *" or empty (don't care). */
3128                 int gamenum = atoi(star_match[0]);
3129                 char *whitename, *blackname, *why, *endtoken;
3130                 ChessMove endtype = (ChessMove) 0;
3131
3132                 if (tkind == 0) {
3133                   whitename = star_match[1];
3134                   blackname = star_match[2];
3135                   why = star_match[3];
3136                   endtoken = star_match[4];
3137                 } else {
3138                   whitename = star_match[1];
3139                   blackname = star_match[3];
3140                   why = star_match[5];
3141                   endtoken = star_match[6];
3142                 }
3143
3144                 /* Game start messages */
3145                 if (strncmp(why, "Creating ", 9) == 0 ||
3146                     strncmp(why, "Continuing ", 11) == 0) {
3147                     gs_gamenum = gamenum;
3148                     strcpy(gs_kind, strchr(why, ' ') + 1);
3149 #if ZIPPY
3150                     if (appData.zippyPlay) {
3151                         ZippyGameStart(whitename, blackname);
3152                     }
3153 #endif /*ZIPPY*/
3154                     continue;
3155                 }
3156
3157                 /* Game end messages */
3158                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3159                     ics_gamenum != gamenum) {
3160                     continue;
3161                 }
3162                 while (endtoken[0] == ' ') endtoken++;
3163                 switch (endtoken[0]) {
3164                   case '*':
3165                   default:
3166                     endtype = GameUnfinished;
3167                     break;
3168                   case '0':
3169                     endtype = BlackWins;
3170                     break;
3171                   case '1':
3172                     if (endtoken[1] == '/')
3173                       endtype = GameIsDrawn;
3174                     else
3175                       endtype = WhiteWins;
3176                     break;
3177                 }
3178                 GameEnds(endtype, why, GE_ICS);
3179 #if ZIPPY
3180                 if (appData.zippyPlay && first.initDone) {
3181                     ZippyGameEnd(endtype, why);
3182                     if (first.pr == NULL) {
3183                       /* Start the next process early so that we'll
3184                          be ready for the next challenge */
3185                       StartChessProgram(&first);
3186                     }
3187                     /* Send "new" early, in case this command takes
3188                        a long time to finish, so that we'll be ready
3189                        for the next challenge. */
3190                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3191                     Reset(TRUE, TRUE);
3192                 }
3193 #endif /*ZIPPY*/
3194                 continue;
3195             }
3196
3197             if (looking_at(buf, &i, "Removing game * from observation") ||
3198                 looking_at(buf, &i, "no longer observing game *") ||
3199                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3200                 if (gameMode == IcsObserving &&
3201                     atoi(star_match[0]) == ics_gamenum)
3202                   {
3203                       /* icsEngineAnalyze */
3204                       if (appData.icsEngineAnalyze) {
3205                             ExitAnalyzeMode();
3206                             ModeHighlight();
3207                       }
3208                       StopClocks();
3209                       gameMode = IcsIdle;
3210                       ics_gamenum = -1;
3211                       ics_user_moved = FALSE;
3212                   }
3213                 continue;
3214             }
3215
3216             if (looking_at(buf, &i, "no longer examining game *")) {
3217                 if (gameMode == IcsExamining &&
3218                     atoi(star_match[0]) == ics_gamenum)
3219                   {
3220                       gameMode = IcsIdle;
3221                       ics_gamenum = -1;
3222                       ics_user_moved = FALSE;
3223                   }
3224                 continue;
3225             }
3226
3227             /* Advance leftover_start past any newlines we find,
3228                so only partial lines can get reparsed */
3229             if (looking_at(buf, &i, "\n")) {
3230                 prevColor = curColor;
3231                 if (curColor != ColorNormal) {
3232                     if (oldi > next_out) {
3233                         SendToPlayer(&buf[next_out], oldi - next_out);
3234                         next_out = oldi;
3235                     }
3236                     Colorize(ColorNormal, FALSE);
3237                     curColor = ColorNormal;
3238                 }
3239                 if (started == STARTED_BOARD) {
3240                     started = STARTED_NONE;
3241                     parse[parse_pos] = NULLCHAR;
3242                     ParseBoard12(parse);
3243                     ics_user_moved = 0;
3244
3245                     /* Send premove here */
3246                     if (appData.premove) {
3247                       char str[MSG_SIZ];
3248                       if (currentMove == 0 &&
3249                           gameMode == IcsPlayingWhite &&
3250                           appData.premoveWhite) {
3251                         sprintf(str, "%s\n", appData.premoveWhiteText);
3252                         if (appData.debugMode)
3253                           fprintf(debugFP, "Sending premove:\n");
3254                         SendToICS(str);
3255                       } else if (currentMove == 1 &&
3256                                  gameMode == IcsPlayingBlack &&
3257                                  appData.premoveBlack) {
3258                         sprintf(str, "%s\n", appData.premoveBlackText);
3259                         if (appData.debugMode)
3260                           fprintf(debugFP, "Sending premove:\n");
3261                         SendToICS(str);
3262                       } else if (gotPremove) {
3263                         gotPremove = 0;
3264                         ClearPremoveHighlights();
3265                         if (appData.debugMode)
3266                           fprintf(debugFP, "Sending premove:\n");
3267                           UserMoveEvent(premoveFromX, premoveFromY, 
3268                                         premoveToX, premoveToY, 
3269                                         premovePromoChar);
3270                       }
3271                     }
3272
3273                     /* Usually suppress following prompt */
3274                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3275                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3276                         if (looking_at(buf, &i, "*% ")) {
3277                             savingComment = FALSE;
3278                         }
3279                     }
3280                     next_out = i;
3281                 } else if (started == STARTED_HOLDINGS) {
3282                     int gamenum;
3283                     char new_piece[MSG_SIZ];
3284                     started = STARTED_NONE;
3285                     parse[parse_pos] = NULLCHAR;
3286                     if (appData.debugMode)
3287                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3288                                                         parse, currentMove);
3289                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3290                         gamenum == ics_gamenum) {
3291                         if (gameInfo.variant == VariantNormal) {
3292                           /* [HGM] We seem to switch variant during a game!
3293                            * Presumably no holdings were displayed, so we have
3294                            * to move the position two files to the right to
3295                            * create room for them!
3296                            */
3297                           VariantClass newVariant;
3298                           switch(gameInfo.boardWidth) { // base guess on board width
3299                                 case 9:  newVariant = VariantShogi; break;
3300                                 case 10: newVariant = VariantGreat; break;
3301                                 default: newVariant = VariantCrazyhouse; break;
3302                           }
3303                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3304                           /* Get a move list just to see the header, which
3305                              will tell us whether this is really bug or zh */
3306                           if (ics_getting_history == H_FALSE) {
3307                             ics_getting_history = H_REQUESTED;
3308                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3309                             SendToICS(str);
3310                           }
3311                         }
3312                         new_piece[0] = NULLCHAR;
3313                         sscanf(parse, "game %d white [%s black [%s <- %s",
3314                                &gamenum, white_holding, black_holding,
3315                                new_piece);
3316                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3317                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3318                         /* [HGM] copy holdings to board holdings area */
3319                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3320                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3321                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3322 #if ZIPPY
3323                         if (appData.zippyPlay && first.initDone) {
3324                             ZippyHoldings(white_holding, black_holding,
3325                                           new_piece);
3326                         }
3327 #endif /*ZIPPY*/
3328                         if (tinyLayout || smallLayout) {
3329                             char wh[16], bh[16];
3330                             PackHolding(wh, white_holding);
3331                             PackHolding(bh, black_holding);
3332                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3333                                     gameInfo.white, gameInfo.black);
3334                         } else {
3335                             sprintf(str, "%s [%s] vs. %s [%s]",
3336                                     gameInfo.white, white_holding,
3337                                     gameInfo.black, black_holding);
3338                         }
3339
3340                         DrawPosition(FALSE, boards[currentMove]);
3341                         DisplayTitle(str);
3342                     }
3343                     /* Suppress following prompt */
3344                     if (looking_at(buf, &i, "*% ")) {
3345                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3346                         savingComment = FALSE;
3347                     }
3348                     next_out = i;
3349                 }
3350                 continue;
3351             }
3352
3353             i++;                /* skip unparsed character and loop back */
3354         }
3355         
3356         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3357             started != STARTED_HOLDINGS && i > next_out) {
3358             SendToPlayer(&buf[next_out], i - next_out);
3359             next_out = i;
3360         }
3361         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3362         
3363         leftover_len = buf_len - leftover_start;
3364         /* if buffer ends with something we couldn't parse,
3365            reparse it after appending the next read */
3366         
3367     } else if (count == 0) {
3368         RemoveInputSource(isr);
3369         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3370     } else {
3371         DisplayFatalError(_("Error reading from ICS"), error, 1);
3372     }
3373 }
3374
3375
3376 /* Board style 12 looks like this:
3377    
3378    <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
3379    
3380  * The "<12> " is stripped before it gets to this routine.  The two
3381  * trailing 0's (flip state and clock ticking) are later addition, and
3382  * some chess servers may not have them, or may have only the first.
3383  * Additional trailing fields may be added in the future.  
3384  */
3385
3386 #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"
3387
3388 #define RELATION_OBSERVING_PLAYED    0
3389 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3390 #define RELATION_PLAYING_MYMOVE      1
3391 #define RELATION_PLAYING_NOTMYMOVE  -1
3392 #define RELATION_EXAMINING           2
3393 #define RELATION_ISOLATED_BOARD     -3
3394 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3395
3396 void
3397 ParseBoard12(string)
3398      char *string;
3399
3400     GameMode newGameMode;
3401     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3402     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3403     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3404     char to_play, board_chars[200];
3405     char move_str[500], str[500], elapsed_time[500];
3406     char black[32], white[32];
3407     Board board;
3408     int prevMove = currentMove;
3409     int ticking = 2;
3410     ChessMove moveType;
3411     int fromX, fromY, toX, toY;
3412     char promoChar;
3413     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3414     char *bookHit = NULL; // [HGM] book
3415     Boolean weird = FALSE, reqFlag = FALSE;
3416
3417     fromX = fromY = toX = toY = -1;
3418     
3419     newGame = FALSE;
3420
3421     if (appData.debugMode)
3422       fprintf(debugFP, _("Parsing board: %s\n"), string);
3423
3424     move_str[0] = NULLCHAR;
3425     elapsed_time[0] = NULLCHAR;
3426     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3427         int  i = 0, j;
3428         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3429             if(string[i] == ' ') { ranks++; files = 0; }
3430             else files++;
3431             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3432             i++;
3433         }
3434         for(j = 0; j <i; j++) board_chars[j] = string[j];
3435         board_chars[i] = '\0';
3436         string += i + 1;
3437     }
3438     n = sscanf(string, PATTERN, &to_play, &double_push,
3439                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3440                &gamenum, white, black, &relation, &basetime, &increment,
3441                &white_stren, &black_stren, &white_time, &black_time,
3442                &moveNum, str, elapsed_time, move_str, &ics_flip,
3443                &ticking);
3444
3445     if (n < 21) {
3446         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3447         DisplayError(str, 0);
3448         return;
3449     }
3450
3451     /* Convert the move number to internal form */
3452     moveNum = (moveNum - 1) * 2;
3453     if (to_play == 'B') moveNum++;
3454     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3455       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3456                         0, 1);
3457       return;
3458     }
3459     
3460     switch (relation) {
3461       case RELATION_OBSERVING_PLAYED:
3462       case RELATION_OBSERVING_STATIC:
3463         if (gamenum == -1) {
3464             /* Old ICC buglet */
3465             relation = RELATION_OBSERVING_STATIC;
3466         }
3467         newGameMode = IcsObserving;
3468         break;
3469       case RELATION_PLAYING_MYMOVE:
3470       case RELATION_PLAYING_NOTMYMOVE:
3471         newGameMode =
3472           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3473             IcsPlayingWhite : IcsPlayingBlack;
3474         break;
3475       case RELATION_EXAMINING:
3476         newGameMode = IcsExamining;
3477         break;
3478       case RELATION_ISOLATED_BOARD:
3479       default:
3480         /* Just display this board.  If user was doing something else,
3481            we will forget about it until the next board comes. */ 
3482         newGameMode = IcsIdle;
3483         break;
3484       case RELATION_STARTING_POSITION:
3485         newGameMode = gameMode;
3486         break;
3487     }
3488     
3489     /* Modify behavior for initial board display on move listing
3490        of wild games.
3491        */
3492     switch (ics_getting_history) {
3493       case H_FALSE:
3494       case H_REQUESTED:
3495         break;
3496       case H_GOT_REQ_HEADER:
3497       case H_GOT_UNREQ_HEADER:
3498         /* This is the initial position of the current game */
3499         gamenum = ics_gamenum;
3500         moveNum = 0;            /* old ICS bug workaround */
3501         if (to_play == 'B') {
3502           startedFromSetupPosition = TRUE;
3503           blackPlaysFirst = TRUE;
3504           moveNum = 1;
3505           if (forwardMostMove == 0) forwardMostMove = 1;
3506           if (backwardMostMove == 0) backwardMostMove = 1;
3507           if (currentMove == 0) currentMove = 1;
3508         }
3509         newGameMode = gameMode;
3510         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3511         break;
3512       case H_GOT_UNWANTED_HEADER:
3513         /* This is an initial board that we don't want */
3514         return;
3515       case H_GETTING_MOVES:
3516         /* Should not happen */
3517         DisplayError(_("Error gathering move list: extra board"), 0);
3518         ics_getting_history = H_FALSE;
3519         return;
3520     }
3521
3522    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3523                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3524      /* [HGM] We seem to have switched variant unexpectedly
3525       * Try to guess new variant from board size
3526       */
3527           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3528           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3529           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3530           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3531           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3532           if(!weird) newVariant = VariantNormal;
3533           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3534           /* Get a move list just to see the header, which
3535              will tell us whether this is really bug or zh */
3536           if (ics_getting_history == H_FALSE) {
3537             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3538             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3539             SendToICS(str);
3540           }
3541     }
3542     
3543     /* Take action if this is the first board of a new game, or of a
3544        different game than is currently being displayed.  */
3545     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3546         relation == RELATION_ISOLATED_BOARD) {
3547         
3548         /* Forget the old game and get the history (if any) of the new one */
3549         if (gameMode != BeginningOfGame) {
3550           Reset(TRUE, TRUE);
3551         }
3552         newGame = TRUE;
3553         if (appData.autoRaiseBoard) BoardToTop();
3554         prevMove = -3;
3555         if (gamenum == -1) {
3556             newGameMode = IcsIdle;
3557         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3558                    appData.getMoveList && !reqFlag) {
3559             /* Need to get game history */
3560             ics_getting_history = H_REQUESTED;
3561             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3562             SendToICS(str);
3563         }
3564         
3565         /* Initially flip the board to have black on the bottom if playing
3566            black or if the ICS flip flag is set, but let the user change
3567            it with the Flip View button. */
3568         flipView = appData.autoFlipView ? 
3569           (newGameMode == IcsPlayingBlack) || ics_flip :
3570           appData.flipView;
3571         
3572         /* Done with values from previous mode; copy in new ones */
3573         gameMode = newGameMode;
3574         ModeHighlight();
3575         ics_gamenum = gamenum;
3576         if (gamenum == gs_gamenum) {
3577             int klen = strlen(gs_kind);
3578             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3579             sprintf(str, "ICS %s", gs_kind);
3580             gameInfo.event = StrSave(str);
3581         } else {
3582             gameInfo.event = StrSave("ICS game");
3583         }
3584         gameInfo.site = StrSave(appData.icsHost);
3585         gameInfo.date = PGNDate();
3586         gameInfo.round = StrSave("-");
3587         gameInfo.white = StrSave(white);
3588         gameInfo.black = StrSave(black);
3589         timeControl = basetime * 60 * 1000;
3590         timeControl_2 = 0;
3591         timeIncrement = increment * 1000;
3592         movesPerSession = 0;
3593         gameInfo.timeControl = TimeControlTagValue();
3594         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3595   if (appData.debugMode) {
3596     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3597     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3598     setbuf(debugFP, NULL);
3599   }
3600
3601         gameInfo.outOfBook = NULL;
3602         
3603         /* Do we have the ratings? */
3604         if (strcmp(player1Name, white) == 0 &&
3605             strcmp(player2Name, black) == 0) {
3606             if (appData.debugMode)
3607               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3608                       player1Rating, player2Rating);
3609             gameInfo.whiteRating = player1Rating;
3610             gameInfo.blackRating = player2Rating;
3611         } else if (strcmp(player2Name, white) == 0 &&
3612                    strcmp(player1Name, black) == 0) {
3613             if (appData.debugMode)
3614               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3615                       player2Rating, player1Rating);
3616             gameInfo.whiteRating = player2Rating;
3617             gameInfo.blackRating = player1Rating;
3618         }
3619         player1Name[0] = player2Name[0] = NULLCHAR;
3620
3621         /* Silence shouts if requested */
3622         if (appData.quietPlay &&
3623             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3624             SendToICS(ics_prefix);
3625             SendToICS("set shout 0\n");
3626         }
3627     }
3628     
3629     /* Deal with midgame name changes */
3630     if (!newGame) {
3631         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3632             if (gameInfo.white) free(gameInfo.white);
3633             gameInfo.white = StrSave(white);
3634         }
3635         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3636             if (gameInfo.black) free(gameInfo.black);
3637             gameInfo.black = StrSave(black);
3638         }
3639     }
3640     
3641     /* Throw away game result if anything actually changes in examine mode */
3642     if (gameMode == IcsExamining && !newGame) {
3643         gameInfo.result = GameUnfinished;
3644         if (gameInfo.resultDetails != NULL) {
3645             free(gameInfo.resultDetails);
3646             gameInfo.resultDetails = NULL;
3647         }
3648     }
3649     
3650     /* In pausing && IcsExamining mode, we ignore boards coming
3651        in if they are in a different variation than we are. */
3652     if (pauseExamInvalid) return;
3653     if (pausing && gameMode == IcsExamining) {
3654         if (moveNum <= pauseExamForwardMostMove) {
3655             pauseExamInvalid = TRUE;
3656             forwardMostMove = pauseExamForwardMostMove;
3657             return;
3658         }
3659     }
3660     
3661   if (appData.debugMode) {
3662     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3663   }
3664     /* Parse the board */
3665     for (k = 0; k < ranks; k++) {
3666       for (j = 0; j < files; j++)
3667         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3668       if(gameInfo.holdingsWidth > 1) {
3669            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3670            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3671       }
3672     }
3673     CopyBoard(boards[moveNum], board);
3674     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3675     if (moveNum == 0) {
3676         startedFromSetupPosition =
3677           !CompareBoards(board, initialPosition);
3678         if(startedFromSetupPosition)
3679             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3680     }
3681
3682     /* [HGM] Set castling rights. Take the outermost Rooks,
3683        to make it also work for FRC opening positions. Note that board12
3684        is really defective for later FRC positions, as it has no way to
3685        indicate which Rook can castle if they are on the same side of King.
3686        For the initial position we grant rights to the outermost Rooks,
3687        and remember thos rights, and we then copy them on positions
3688        later in an FRC game. This means WB might not recognize castlings with
3689        Rooks that have moved back to their original position as illegal,
3690        but in ICS mode that is not its job anyway.
3691     */
3692     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3693     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3694
3695         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3696             if(board[0][i] == WhiteRook) j = i;
3697         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3698         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3699             if(board[0][i] == WhiteRook) j = i;
3700         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3701         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3702             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3703         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3704         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3705             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3706         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3707
3708         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3709         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3710             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3711         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3712             if(board[BOARD_HEIGHT-1][k] == bKing)
3713                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3714     } else { int r;
3715         r = boards[moveNum][CASTLING][0] = initialRights[0];
3716         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3717         r = boards[moveNum][CASTLING][1] = initialRights[1];
3718         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3719         r = boards[moveNum][CASTLING][3] = initialRights[3];
3720         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3721         r = boards[moveNum][CASTLING][4] = initialRights[4];
3722         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3723         /* wildcastle kludge: always assume King has rights */
3724         r = boards[moveNum][CASTLING][2] = initialRights[2];
3725         r = boards[moveNum][CASTLING][5] = initialRights[5];
3726     }
3727     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3728     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3729
3730     
3731     if (ics_getting_history == H_GOT_REQ_HEADER ||
3732         ics_getting_history == H_GOT_UNREQ_HEADER) {
3733         /* This was an initial position from a move list, not
3734            the current position */
3735         return;
3736     }
3737     
3738     /* Update currentMove and known move number limits */
3739     newMove = newGame || moveNum > forwardMostMove;
3740
3741     if (newGame) {
3742         forwardMostMove = backwardMostMove = currentMove = moveNum;
3743         if (gameMode == IcsExamining && moveNum == 0) {
3744           /* Workaround for ICS limitation: we are not told the wild
3745              type when starting to examine a game.  But if we ask for
3746              the move list, the move list header will tell us */
3747             ics_getting_history = H_REQUESTED;
3748             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3749             SendToICS(str);
3750         }
3751     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3752                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3753 #if ZIPPY
3754         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3755         /* [HGM] applied this also to an engine that is silently watching        */
3756         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3757             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3758             gameInfo.variant == currentlyInitializedVariant) {
3759           takeback = forwardMostMove - moveNum;
3760           for (i = 0; i < takeback; i++) {
3761             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3762             SendToProgram("undo\n", &first);
3763           }
3764         }
3765 #endif
3766
3767         forwardMostMove = moveNum;
3768         if (!pausing || currentMove > forwardMostMove)
3769           currentMove = forwardMostMove;
3770     } else {
3771         /* New part of history that is not contiguous with old part */ 
3772         if (pausing && gameMode == IcsExamining) {
3773             pauseExamInvalid = TRUE;
3774             forwardMostMove = pauseExamForwardMostMove;
3775             return;
3776         }
3777         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3778 #if ZIPPY
3779             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3780                 // [HGM] when we will receive the move list we now request, it will be
3781                 // fed to the engine from the first move on. So if the engine is not
3782                 // in the initial position now, bring it there.
3783                 InitChessProgram(&first, 0);
3784             }
3785 #endif
3786             ics_getting_history = H_REQUESTED;
3787             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3788             SendToICS(str);
3789         }
3790         forwardMostMove = backwardMostMove = currentMove = moveNum;
3791     }
3792     
3793     /* Update the clocks */
3794     if (strchr(elapsed_time, '.')) {
3795       /* Time is in ms */
3796       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3797       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3798     } else {
3799       /* Time is in seconds */
3800       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3801       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3802     }
3803       
3804
3805 #if ZIPPY
3806     if (appData.zippyPlay && newGame &&
3807         gameMode != IcsObserving && gameMode != IcsIdle &&
3808         gameMode != IcsExamining)
3809       ZippyFirstBoard(moveNum, basetime, increment);
3810 #endif
3811     
3812     /* Put the move on the move list, first converting
3813        to canonical algebraic form. */
3814     if (moveNum > 0) {
3815   if (appData.debugMode) {
3816     if (appData.debugMode) { int f = forwardMostMove;
3817         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3818                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3819                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3820     }
3821     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3822     fprintf(debugFP, "moveNum = %d\n", moveNum);
3823     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3824     setbuf(debugFP, NULL);
3825   }
3826         if (moveNum <= backwardMostMove) {
3827             /* We don't know what the board looked like before
3828                this move.  Punt. */
3829             strcpy(parseList[moveNum - 1], move_str);
3830             strcat(parseList[moveNum - 1], " ");
3831             strcat(parseList[moveNum - 1], elapsed_time);
3832             moveList[moveNum - 1][0] = NULLCHAR;
3833         } else if (strcmp(move_str, "none") == 0) {
3834             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3835             /* Again, we don't know what the board looked like;
3836                this is really the start of the game. */
3837             parseList[moveNum - 1][0] = NULLCHAR;
3838             moveList[moveNum - 1][0] = NULLCHAR;
3839             backwardMostMove = moveNum;
3840             startedFromSetupPosition = TRUE;
3841             fromX = fromY = toX = toY = -1;
3842         } else {
3843           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3844           //                 So we parse the long-algebraic move string in stead of the SAN move
3845           int valid; char buf[MSG_SIZ], *prom;
3846
3847           // str looks something like "Q/a1-a2"; kill the slash
3848           if(str[1] == '/') 
3849                 sprintf(buf, "%c%s", str[0], str+2);
3850           else  strcpy(buf, str); // might be castling
3851           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3852                 strcat(buf, prom); // long move lacks promo specification!
3853           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3854                 if(appData.debugMode) 
3855                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3856                 strcpy(move_str, buf);
3857           }
3858           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3859                                 &fromX, &fromY, &toX, &toY, &promoChar)
3860                || ParseOneMove(buf, moveNum - 1, &moveType,
3861                                 &fromX, &fromY, &toX, &toY, &promoChar);
3862           // end of long SAN patch
3863           if (valid) {
3864             (void) CoordsToAlgebraic(boards[moveNum - 1],
3865                                      PosFlags(moveNum - 1),
3866                                      fromY, fromX, toY, toX, promoChar,
3867                                      parseList[moveNum-1]);
3868             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3869               case MT_NONE:
3870               case MT_STALEMATE:
3871               default:
3872                 break;
3873               case MT_CHECK:
3874                 if(gameInfo.variant != VariantShogi)
3875                     strcat(parseList[moveNum - 1], "+");
3876                 break;
3877               case MT_CHECKMATE:
3878               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3879                 strcat(parseList[moveNum - 1], "#");
3880                 break;
3881             }
3882             strcat(parseList[moveNum - 1], " ");
3883             strcat(parseList[moveNum - 1], elapsed_time);
3884             /* currentMoveString is set as a side-effect of ParseOneMove */
3885             strcpy(moveList[moveNum - 1], currentMoveString);
3886             strcat(moveList[moveNum - 1], "\n");
3887           } else {
3888             /* Move from ICS was illegal!?  Punt. */
3889   if (appData.debugMode) {
3890     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3891     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3892   }
3893             strcpy(parseList[moveNum - 1], move_str);
3894             strcat(parseList[moveNum - 1], " ");
3895             strcat(parseList[moveNum - 1], elapsed_time);
3896             moveList[moveNum - 1][0] = NULLCHAR;
3897             fromX = fromY = toX = toY = -1;
3898           }
3899         }
3900   if (appData.debugMode) {
3901     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3902     setbuf(debugFP, NULL);
3903   }
3904
3905 #if ZIPPY
3906         /* Send move to chess program (BEFORE animating it). */
3907         if (appData.zippyPlay && !newGame && newMove && 
3908            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3909
3910             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3911                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3912                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3913                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3914                             move_str);
3915                     DisplayError(str, 0);
3916                 } else {
3917                     if (first.sendTime) {
3918                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3919                     }
3920                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3921                     if (firstMove && !bookHit) {
3922                         firstMove = FALSE;
3923                         if (first.useColors) {
3924                           SendToProgram(gameMode == IcsPlayingWhite ?
3925                                         "white\ngo\n" :
3926                                         "black\ngo\n", &first);
3927                         } else {
3928                           SendToProgram("go\n", &first);
3929                         }
3930                         first.maybeThinking = TRUE;
3931                     }
3932                 }
3933             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3934               if (moveList[moveNum - 1][0] == NULLCHAR) {
3935                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3936                 DisplayError(str, 0);
3937               } else {
3938                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3939                 SendMoveToProgram(moveNum - 1, &first);
3940               }
3941             }
3942         }
3943 #endif
3944     }
3945
3946     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3947         /* If move comes from a remote source, animate it.  If it
3948            isn't remote, it will have already been animated. */
3949         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3950             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3951         }
3952         if (!pausing && appData.highlightLastMove) {
3953             SetHighlights(fromX, fromY, toX, toY);
3954         }
3955     }
3956     
3957     /* Start the clocks */
3958     whiteFlag = blackFlag = FALSE;
3959     appData.clockMode = !(basetime == 0 && increment == 0);
3960     if (ticking == 0) {
3961       ics_clock_paused = TRUE;
3962       StopClocks();
3963     } else if (ticking == 1) {
3964       ics_clock_paused = FALSE;
3965     }
3966     if (gameMode == IcsIdle ||
3967         relation == RELATION_OBSERVING_STATIC ||
3968         relation == RELATION_EXAMINING ||
3969         ics_clock_paused)
3970       DisplayBothClocks();
3971     else
3972       StartClocks();
3973     
3974     /* Display opponents and material strengths */
3975     if (gameInfo.variant != VariantBughouse &&
3976         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3977         if (tinyLayout || smallLayout) {
3978             if(gameInfo.variant == VariantNormal)
3979                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3980                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3981                     basetime, increment);
3982             else
3983                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3984                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3985                     basetime, increment, (int) gameInfo.variant);
3986         } else {
3987             if(gameInfo.variant == VariantNormal)
3988                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3989                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3990                     basetime, increment);
3991             else
3992                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3993                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3994                     basetime, increment, VariantName(gameInfo.variant));
3995         }
3996         DisplayTitle(str);
3997   if (appData.debugMode) {
3998     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3999   }
4000     }
4001
4002    
4003     /* Display the board */
4004     if (!pausing && !appData.noGUI) {
4005       
4006       if (appData.premove)
4007           if (!gotPremove || 
4008              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4009              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4010               ClearPremoveHighlights();
4011
4012       DrawPosition(FALSE, boards[currentMove]);
4013       DisplayMove(moveNum - 1);
4014       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4015             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4016               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4017         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4018       }
4019     }
4020
4021     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4022 #if ZIPPY
4023     if(bookHit) { // [HGM] book: simulate book reply
4024         static char bookMove[MSG_SIZ]; // a bit generous?
4025
4026         programStats.nodes = programStats.depth = programStats.time = 
4027         programStats.score = programStats.got_only_move = 0;
4028         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4029
4030         strcpy(bookMove, "move ");
4031         strcat(bookMove, bookHit);
4032         HandleMachineMove(bookMove, &first);
4033     }
4034 #endif
4035 }
4036
4037 void
4038 GetMoveListEvent()
4039 {
4040     char buf[MSG_SIZ];
4041     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4042         ics_getting_history = H_REQUESTED;
4043         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4044         SendToICS(buf);
4045     }
4046 }
4047
4048 void
4049 AnalysisPeriodicEvent(force)
4050      int force;
4051 {
4052     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4053          && !force) || !appData.periodicUpdates)
4054       return;
4055
4056     /* Send . command to Crafty to collect stats */
4057     SendToProgram(".\n", &first);
4058
4059     /* Don't send another until we get a response (this makes
4060        us stop sending to old Crafty's which don't understand
4061        the "." command (sending illegal cmds resets node count & time,
4062        which looks bad)) */
4063     programStats.ok_to_send = 0;
4064 }
4065
4066 void ics_update_width(new_width)
4067         int new_width;
4068 {
4069         ics_printf("set width %d\n", new_width);
4070 }
4071
4072 void
4073 SendMoveToProgram(moveNum, cps)
4074      int moveNum;
4075      ChessProgramState *cps;
4076 {
4077     char buf[MSG_SIZ];
4078
4079     if (cps->useUsermove) {
4080       SendToProgram("usermove ", cps);
4081     }
4082     if (cps->useSAN) {
4083       char *space;
4084       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4085         int len = space - parseList[moveNum];
4086         memcpy(buf, parseList[moveNum], len);
4087         buf[len++] = '\n';
4088         buf[len] = NULLCHAR;
4089       } else {
4090         sprintf(buf, "%s\n", parseList[moveNum]);
4091       }
4092       SendToProgram(buf, cps);
4093     } else {
4094       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4095         AlphaRank(moveList[moveNum], 4);
4096         SendToProgram(moveList[moveNum], cps);
4097         AlphaRank(moveList[moveNum], 4); // and back
4098       } else
4099       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4100        * the engine. It would be nice to have a better way to identify castle 
4101        * moves here. */
4102       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4103                                                                          && cps->useOOCastle) {
4104         int fromX = moveList[moveNum][0] - AAA; 
4105         int fromY = moveList[moveNum][1] - ONE;
4106         int toX = moveList[moveNum][2] - AAA; 
4107         int toY = moveList[moveNum][3] - ONE;
4108         if((boards[moveNum][fromY][fromX] == WhiteKing 
4109             && boards[moveNum][toY][toX] == WhiteRook)
4110            || (boards[moveNum][fromY][fromX] == BlackKing 
4111                && boards[moveNum][toY][toX] == BlackRook)) {
4112           if(toX > fromX) SendToProgram("O-O\n", cps);
4113           else SendToProgram("O-O-O\n", cps);
4114         }
4115         else SendToProgram(moveList[moveNum], cps);
4116       }
4117       else SendToProgram(moveList[moveNum], cps);
4118       /* End of additions by Tord */
4119     }
4120
4121     /* [HGM] setting up the opening has brought engine in force mode! */
4122     /*       Send 'go' if we are in a mode where machine should play. */
4123     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4124         (gameMode == TwoMachinesPlay   ||
4125 #ifdef ZIPPY
4126          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4127 #endif
4128          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4129         SendToProgram("go\n", cps);
4130   if (appData.debugMode) {
4131     fprintf(debugFP, "(extra)\n");
4132   }
4133     }
4134     setboardSpoiledMachineBlack = 0;
4135 }
4136
4137 void
4138 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4139      ChessMove moveType;
4140      int fromX, fromY, toX, toY;
4141 {
4142     char user_move[MSG_SIZ];
4143
4144     switch (moveType) {
4145       default:
4146         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4147                 (int)moveType, fromX, fromY, toX, toY);
4148         DisplayError(user_move + strlen("say "), 0);
4149         break;
4150       case WhiteKingSideCastle:
4151       case BlackKingSideCastle:
4152       case WhiteQueenSideCastleWild:
4153       case BlackQueenSideCastleWild:
4154       /* PUSH Fabien */
4155       case WhiteHSideCastleFR:
4156       case BlackHSideCastleFR:
4157       /* POP Fabien */
4158         sprintf(user_move, "o-o\n");
4159         break;
4160       case WhiteQueenSideCastle:
4161       case BlackQueenSideCastle:
4162       case WhiteKingSideCastleWild:
4163       case BlackKingSideCastleWild:
4164       /* PUSH Fabien */
4165       case WhiteASideCastleFR:
4166       case BlackASideCastleFR:
4167       /* POP Fabien */
4168         sprintf(user_move, "o-o-o\n");
4169         break;
4170       case WhitePromotionQueen:
4171       case BlackPromotionQueen:
4172       case WhitePromotionRook:
4173       case BlackPromotionRook:
4174       case WhitePromotionBishop:
4175       case BlackPromotionBishop:
4176       case WhitePromotionKnight:
4177       case BlackPromotionKnight:
4178       case WhitePromotionKing:
4179       case BlackPromotionKing:
4180       case WhitePromotionChancellor:
4181       case BlackPromotionChancellor:
4182       case WhitePromotionArchbishop:
4183       case BlackPromotionArchbishop:
4184         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4185             sprintf(user_move, "%c%c%c%c=%c\n",
4186                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4187                 PieceToChar(WhiteFerz));
4188         else if(gameInfo.variant == VariantGreat)
4189             sprintf(user_move, "%c%c%c%c=%c\n",
4190                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4191                 PieceToChar(WhiteMan));
4192         else
4193             sprintf(user_move, "%c%c%c%c=%c\n",
4194                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4195                 PieceToChar(PromoPiece(moveType)));
4196         break;
4197       case WhiteDrop:
4198       case BlackDrop:
4199         sprintf(user_move, "%c@%c%c\n",
4200                 ToUpper(PieceToChar((ChessSquare) fromX)),
4201                 AAA + toX, ONE + toY);
4202         break;
4203       case NormalMove:
4204       case WhiteCapturesEnPassant:
4205       case BlackCapturesEnPassant:
4206       case IllegalMove:  /* could be a variant we don't quite understand */
4207         sprintf(user_move, "%c%c%c%c\n",
4208                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4209         break;
4210     }
4211     SendToICS(user_move);
4212     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4213         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4214 }
4215
4216 void
4217 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4218      int rf, ff, rt, ft;
4219      char promoChar;
4220      char move[7];
4221 {
4222     if (rf == DROP_RANK) {
4223         sprintf(move, "%c@%c%c\n",
4224                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4225     } else {
4226         if (promoChar == 'x' || promoChar == NULLCHAR) {
4227             sprintf(move, "%c%c%c%c\n",
4228                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4229         } else {
4230             sprintf(move, "%c%c%c%c%c\n",
4231                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4232         }
4233     }
4234 }
4235
4236 void
4237 ProcessICSInitScript(f)
4238      FILE *f;
4239 {
4240     char buf[MSG_SIZ];
4241
4242     while (fgets(buf, MSG_SIZ, f)) {
4243         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4244     }
4245
4246     fclose(f);
4247 }
4248
4249
4250 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4251 void
4252 AlphaRank(char *move, int n)
4253 {
4254 //    char *p = move, c; int x, y;
4255
4256     if (appData.debugMode) {
4257         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4258     }
4259
4260     if(move[1]=='*' && 
4261        move[2]>='0' && move[2]<='9' &&
4262        move[3]>='a' && move[3]<='x'    ) {
4263         move[1] = '@';
4264         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4265         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4266     } else
4267     if(move[0]>='0' && move[0]<='9' &&
4268        move[1]>='a' && move[1]<='x' &&
4269        move[2]>='0' && move[2]<='9' &&
4270        move[3]>='a' && move[3]<='x'    ) {
4271         /* input move, Shogi -> normal */
4272         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4273         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4274         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4275         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4276     } else
4277     if(move[1]=='@' &&
4278        move[3]>='0' && move[3]<='9' &&
4279        move[2]>='a' && move[2]<='x'    ) {
4280         move[1] = '*';
4281         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4282         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4283     } else
4284     if(
4285        move[0]>='a' && move[0]<='x' &&
4286        move[3]>='0' && move[3]<='9' &&
4287        move[2]>='a' && move[2]<='x'    ) {
4288          /* output move, normal -> Shogi */
4289         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4290         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4291         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4292         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4293         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4294     }
4295     if (appData.debugMode) {
4296         fprintf(debugFP, "   out = '%s'\n", move);
4297     }
4298 }
4299
4300 /* Parser for moves from gnuchess, ICS, or user typein box */
4301 Boolean
4302 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4303      char *move;
4304      int moveNum;
4305      ChessMove *moveType;
4306      int *fromX, *fromY, *toX, *toY;
4307      char *promoChar;
4308 {       
4309     if (appData.debugMode) {
4310         fprintf(debugFP, "move to parse: %s\n", move);
4311     }
4312     *moveType = yylexstr(moveNum, move);
4313
4314     switch (*moveType) {
4315       case WhitePromotionChancellor:
4316       case BlackPromotionChancellor:
4317       case WhitePromotionArchbishop:
4318       case BlackPromotionArchbishop:
4319       case WhitePromotionQueen:
4320       case BlackPromotionQueen:
4321       case WhitePromotionRook:
4322       case BlackPromotionRook:
4323       case WhitePromotionBishop:
4324       case BlackPromotionBishop:
4325       case WhitePromotionKnight:
4326       case BlackPromotionKnight:
4327       case WhitePromotionKing:
4328       case BlackPromotionKing:
4329       case NormalMove:
4330       case WhiteCapturesEnPassant:
4331       case BlackCapturesEnPassant:
4332       case WhiteKingSideCastle:
4333       case WhiteQueenSideCastle:
4334       case BlackKingSideCastle:
4335       case BlackQueenSideCastle:
4336       case WhiteKingSideCastleWild:
4337       case WhiteQueenSideCastleWild:
4338       case BlackKingSideCastleWild:
4339       case BlackQueenSideCastleWild:
4340       /* Code added by Tord: */
4341       case WhiteHSideCastleFR:
4342       case WhiteASideCastleFR:
4343       case BlackHSideCastleFR:
4344       case BlackASideCastleFR:
4345       /* End of code added by Tord */
4346       case IllegalMove:         /* bug or odd chess variant */
4347         *fromX = currentMoveString[0] - AAA;
4348         *fromY = currentMoveString[1] - ONE;
4349         *toX = currentMoveString[2] - AAA;
4350         *toY = currentMoveString[3] - ONE;
4351         *promoChar = currentMoveString[4];
4352         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4353             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4354     if (appData.debugMode) {
4355         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4356     }
4357             *fromX = *fromY = *toX = *toY = 0;
4358             return FALSE;
4359         }
4360         if (appData.testLegality) {
4361           return (*moveType != IllegalMove);
4362         } else {
4363           return !(fromX == fromY && toX == toY);
4364         }
4365
4366       case WhiteDrop:
4367       case BlackDrop:
4368         *fromX = *moveType == WhiteDrop ?
4369           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4370           (int) CharToPiece(ToLower(currentMoveString[0]));
4371         *fromY = DROP_RANK;
4372         *toX = currentMoveString[2] - AAA;
4373         *toY = currentMoveString[3] - ONE;
4374         *promoChar = NULLCHAR;
4375         return TRUE;
4376
4377       case AmbiguousMove:
4378       case ImpossibleMove:
4379       case (ChessMove) 0:       /* end of file */
4380       case ElapsedTime:
4381       case Comment:
4382       case PGNTag:
4383       case NAG:
4384       case WhiteWins:
4385       case BlackWins:
4386       case GameIsDrawn:
4387       default:
4388     if (appData.debugMode) {
4389         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4390     }
4391         /* bug? */
4392         *fromX = *fromY = *toX = *toY = 0;
4393         *promoChar = NULLCHAR;
4394         return FALSE;
4395     }
4396 }
4397
4398 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4399 // All positions will have equal probability, but the current method will not provide a unique
4400 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4401 #define DARK 1
4402 #define LITE 2
4403 #define ANY 3
4404
4405 int squaresLeft[4];
4406 int piecesLeft[(int)BlackPawn];
4407 int seed, nrOfShuffles;
4408
4409 void GetPositionNumber()
4410 {       // sets global variable seed
4411         int i;
4412
4413         seed = appData.defaultFrcPosition;
4414         if(seed < 0) { // randomize based on time for negative FRC position numbers
4415                 for(i=0; i<50; i++) seed += random();
4416                 seed = random() ^ random() >> 8 ^ random() << 8;
4417                 if(seed<0) seed = -seed;
4418         }
4419 }
4420
4421 int put(Board board, int pieceType, int rank, int n, int shade)
4422 // put the piece on the (n-1)-th empty squares of the given shade
4423 {
4424         int i;
4425
4426         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4427                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4428                         board[rank][i] = (ChessSquare) pieceType;
4429                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4430                         squaresLeft[ANY]--;
4431                         piecesLeft[pieceType]--; 
4432                         return i;
4433                 }
4434         }
4435         return -1;
4436 }
4437
4438
4439 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4440 // calculate where the next piece goes, (any empty square), and put it there
4441 {
4442         int i;
4443
4444         i = seed % squaresLeft[shade];
4445         nrOfShuffles *= squaresLeft[shade];
4446         seed /= squaresLeft[shade];
4447         put(board, pieceType, rank, i, shade);
4448 }
4449
4450 void AddTwoPieces(Board board, int pieceType, int rank)
4451 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4452 {
4453         int i, n=squaresLeft[ANY], j=n-1, k;
4454
4455         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4456         i = seed % k;  // pick one
4457         nrOfShuffles *= k;
4458         seed /= k;
4459         while(i >= j) i -= j--;
4460         j = n - 1 - j; i += j;
4461         put(board, pieceType, rank, j, ANY);
4462         put(board, pieceType, rank, i, ANY);
4463 }
4464
4465 void SetUpShuffle(Board board, int number)
4466 {
4467         int i, p, first=1;
4468
4469         GetPositionNumber(); nrOfShuffles = 1;
4470
4471         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4472         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4473         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4474
4475         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4476
4477         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4478             p = (int) board[0][i];
4479             if(p < (int) BlackPawn) piecesLeft[p] ++;
4480             board[0][i] = EmptySquare;
4481         }
4482
4483         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4484             // shuffles restricted to allow normal castling put KRR first
4485             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4486                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4487             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4488                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4489             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4490                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4491             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4492                 put(board, WhiteRook, 0, 0, ANY);
4493             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4494         }
4495
4496         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4497             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4498             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4499                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4500                 while(piecesLeft[p] >= 2) {
4501                     AddOnePiece(board, p, 0, LITE);
4502                     AddOnePiece(board, p, 0, DARK);
4503                 }
4504                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4505             }
4506
4507         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4508             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4509             // but we leave King and Rooks for last, to possibly obey FRC restriction
4510             if(p == (int)WhiteRook) continue;
4511             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4512             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4513         }
4514
4515         // now everything is placed, except perhaps King (Unicorn) and Rooks
4516
4517         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4518             // Last King gets castling rights
4519             while(piecesLeft[(int)WhiteUnicorn]) {
4520                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4521                 initialRights[2]  = initialRights[5]  = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4522             }
4523
4524             while(piecesLeft[(int)WhiteKing]) {
4525                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4526                 initialRights[2]  = initialRights[5]  = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4527             }
4528
4529
4530         } else {
4531             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4532             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4533         }
4534
4535         // Only Rooks can be left; simply place them all
4536         while(piecesLeft[(int)WhiteRook]) {
4537                 i = put(board, WhiteRook, 0, 0, ANY);
4538                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4539                         if(first) {
4540                                 first=0;
4541                                 initialRights[1]  = initialRights[4]  = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
4542                         }
4543                         initialRights[0]  = initialRights[3]  = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
4544                 }
4545         }
4546         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4547             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4548         }
4549
4550         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4551 }
4552
4553 int SetCharTable( char *table, const char * map )
4554 /* [HGM] moved here from winboard.c because of its general usefulness */
4555 /*       Basically a safe strcpy that uses the last character as King */
4556 {
4557     int result = FALSE; int NrPieces;
4558
4559     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4560                     && NrPieces >= 12 && !(NrPieces&1)) {
4561         int i; /* [HGM] Accept even length from 12 to 34 */
4562
4563         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4564         for( i=0; i<NrPieces/2-1; i++ ) {
4565             table[i] = map[i];
4566             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4567         }
4568         table[(int) WhiteKing]  = map[NrPieces/2-1];
4569         table[(int) BlackKing]  = map[NrPieces-1];
4570
4571         result = TRUE;
4572     }
4573
4574     return result;
4575 }
4576
4577 void Prelude(Board board)
4578 {       // [HGM] superchess: random selection of exo-pieces
4579         int i, j, k; ChessSquare p; 
4580         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4581
4582         GetPositionNumber(); // use FRC position number
4583
4584         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4585             SetCharTable(pieceToChar, appData.pieceToCharTable);
4586             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4587                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4588         }
4589
4590         j = seed%4;                 seed /= 4; 
4591         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4592         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4593         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4594         j = seed%3 + (seed%3 >= j); seed /= 3; 
4595         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4596         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4597         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4598         j = seed%3;                 seed /= 3; 
4599         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4600         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4601         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4602         j = seed%2 + (seed%2 >= j); seed /= 2; 
4603         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4604         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4605         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4606         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4607         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4608         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4609         put(board, exoPieces[0],    0, 0, ANY);
4610         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4611 }
4612
4613 void
4614 InitPosition(redraw)
4615      int redraw;
4616 {
4617     ChessSquare (* pieces)[BOARD_FILES];
4618     int i, j, pawnRow, overrule,
4619     oldx = gameInfo.boardWidth,
4620     oldy = gameInfo.boardHeight,
4621     oldh = gameInfo.holdingsWidth,
4622     oldv = gameInfo.variant;
4623
4624     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4625
4626     /* [AS] Initialize pv info list [HGM] and game status */
4627     {
4628         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4629             pvInfoList[i].depth = 0;
4630             boards[i][EP_STATUS] = EP_NONE;
4631             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4632         }
4633
4634         initialRulePlies = 0; /* 50-move counter start */
4635
4636         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4637         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4638     }
4639
4640     
4641     /* [HGM] logic here is completely changed. In stead of full positions */
4642     /* the initialized data only consist of the two backranks. The switch */
4643     /* selects which one we will use, which is than copied to the Board   */
4644     /* initialPosition, which for the rest is initialized by Pawns and    */
4645     /* empty squares. This initial position is then copied to boards[0],  */
4646     /* possibly after shuffling, so that it remains available.            */
4647
4648     gameInfo.holdingsWidth = 0; /* default board sizes */
4649     gameInfo.boardWidth    = 8;
4650     gameInfo.boardHeight   = 8;
4651     gameInfo.holdingsSize  = 0;
4652     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4653     for(i=0; i<BOARD_FILES-2; i++)
4654       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4655     initialPosition[EP_STATUS] = EP_NONE;
4656     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4657
4658     switch (gameInfo.variant) {
4659     case VariantFischeRandom:
4660       shuffleOpenings = TRUE;
4661     default:
4662       pieces = FIDEArray;
4663       break;
4664     case VariantShatranj:
4665       pieces = ShatranjArray;
4666       nrCastlingRights = 0;
4667       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4668       break;
4669     case VariantTwoKings:
4670       pieces = twoKingsArray;
4671       break;
4672     case VariantCapaRandom:
4673       shuffleOpenings = TRUE;
4674     case VariantCapablanca:
4675       pieces = CapablancaArray;
4676       gameInfo.boardWidth = 10;
4677       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4678       break;
4679     case VariantGothic:
4680       pieces = GothicArray;
4681       gameInfo.boardWidth = 10;
4682       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4683       break;
4684     case VariantJanus:
4685       pieces = JanusArray;
4686       gameInfo.boardWidth = 10;
4687       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4688       nrCastlingRights = 6;
4689         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4690         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4691         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4692         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4693         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4694         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4695       break;
4696     case VariantFalcon:
4697       pieces = FalconArray;
4698       gameInfo.boardWidth = 10;
4699       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4700       break;
4701     case VariantXiangqi:
4702       pieces = XiangqiArray;
4703       gameInfo.boardWidth  = 9;
4704       gameInfo.boardHeight = 10;
4705       nrCastlingRights = 0;
4706       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4707       break;
4708     case VariantShogi:
4709       pieces = ShogiArray;
4710       gameInfo.boardWidth  = 9;
4711       gameInfo.boardHeight = 9;
4712       gameInfo.holdingsSize = 7;
4713       nrCastlingRights = 0;
4714       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4715       break;
4716     case VariantCourier:
4717       pieces = CourierArray;
4718       gameInfo.boardWidth  = 12;
4719       nrCastlingRights = 0;
4720       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4721       break;
4722     case VariantKnightmate:
4723       pieces = KnightmateArray;
4724       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4725       break;
4726     case VariantFairy:
4727       pieces = fairyArray;
4728       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4729       break;
4730     case VariantGreat:
4731       pieces = GreatArray;
4732       gameInfo.boardWidth = 10;
4733       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4734       gameInfo.holdingsSize = 8;
4735       break;
4736     case VariantSuper:
4737       pieces = FIDEArray;
4738       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4739       gameInfo.holdingsSize = 8;
4740       startedFromSetupPosition = TRUE;
4741       break;
4742     case VariantCrazyhouse:
4743     case VariantBughouse:
4744       pieces = FIDEArray;
4745       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4746       gameInfo.holdingsSize = 5;
4747       break;
4748     case VariantWildCastle:
4749       pieces = FIDEArray;
4750       /* !!?shuffle with kings guaranteed to be on d or e file */
4751       shuffleOpenings = 1;
4752       break;
4753     case VariantNoCastle:
4754       pieces = FIDEArray;
4755       nrCastlingRights = 0;
4756       /* !!?unconstrained back-rank shuffle */
4757       shuffleOpenings = 1;
4758       break;
4759     }
4760
4761     overrule = 0;
4762     if(appData.NrFiles >= 0) {
4763         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4764         gameInfo.boardWidth = appData.NrFiles;
4765     }
4766     if(appData.NrRanks >= 0) {
4767         gameInfo.boardHeight = appData.NrRanks;
4768     }
4769     if(appData.holdingsSize >= 0) {
4770         i = appData.holdingsSize;
4771         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4772         gameInfo.holdingsSize = i;
4773     }
4774     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4775     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4776         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4777
4778     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4779     if(pawnRow < 1) pawnRow = 1;
4780
4781     /* User pieceToChar list overrules defaults */
4782     if(appData.pieceToCharTable != NULL)
4783         SetCharTable(pieceToChar, appData.pieceToCharTable);
4784
4785     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4786
4787         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4788             s = (ChessSquare) 0; /* account holding counts in guard band */
4789         for( i=0; i<BOARD_HEIGHT; i++ )
4790             initialPosition[i][j] = s;
4791
4792         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4793         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4794         initialPosition[pawnRow][j] = WhitePawn;
4795         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4796         if(gameInfo.variant == VariantXiangqi) {
4797             if(j&1) {
4798                 initialPosition[pawnRow][j] = 
4799                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4800                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4801                    initialPosition[2][j] = WhiteCannon;
4802                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4803                 }
4804             }
4805         }
4806         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4807     }
4808     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4809
4810             j=BOARD_LEFT+1;
4811             initialPosition[1][j] = WhiteBishop;
4812             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4813             j=BOARD_RGHT-2;
4814             initialPosition[1][j] = WhiteRook;
4815             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4816     }
4817
4818     if( nrCastlingRights == -1) {
4819         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4820         /*       This sets default castling rights from none to normal corners   */
4821         /* Variants with other castling rights must set them themselves above    */
4822         nrCastlingRights = 6;
4823        
4824         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4825         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4826         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4827         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4828         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4829         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4830      }
4831
4832      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4833      if(gameInfo.variant == VariantGreat) { // promotion commoners
4834         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4835         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4836         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4837         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4838      }
4839   if (appData.debugMode) {
4840     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4841   }
4842     if(shuffleOpenings) {
4843         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4844         startedFromSetupPosition = TRUE;
4845     }
4846     if(startedFromPositionFile) {
4847       /* [HGM] loadPos: use PositionFile for every new game */
4848       CopyBoard(initialPosition, filePosition);
4849       for(i=0; i<nrCastlingRights; i++)
4850           initialRights[i] = filePosition[CASTLING][i];
4851       startedFromSetupPosition = TRUE;
4852     }
4853
4854     CopyBoard(boards[0], initialPosition);
4855
4856     if(oldx != gameInfo.boardWidth ||
4857        oldy != gameInfo.boardHeight ||
4858        oldh != gameInfo.holdingsWidth
4859 #ifdef GOTHIC
4860        || oldv == VariantGothic ||        // For licensing popups
4861        gameInfo.variant == VariantGothic
4862 #endif
4863 #ifdef FALCON
4864        || oldv == VariantFalcon ||
4865        gameInfo.variant == VariantFalcon
4866 #endif
4867                                          )
4868             InitDrawingSizes(-2 ,0);
4869
4870     if (redraw)
4871       DrawPosition(TRUE, boards[currentMove]);
4872 }
4873
4874 void
4875 SendBoard(cps, moveNum)
4876      ChessProgramState *cps;
4877      int moveNum;
4878 {
4879     char message[MSG_SIZ];
4880     
4881     if (cps->useSetboard) {
4882       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4883       sprintf(message, "setboard %s\n", fen);
4884       SendToProgram(message, cps);
4885       free(fen);
4886
4887     } else {
4888       ChessSquare *bp;
4889       int i, j;
4890       /* Kludge to set black to move, avoiding the troublesome and now
4891        * deprecated "black" command.
4892        */
4893       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4894
4895       SendToProgram("edit\n", cps);
4896       SendToProgram("#\n", cps);
4897       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4898         bp = &boards[moveNum][i][BOARD_LEFT];
4899         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4900           if ((int) *bp < (int) BlackPawn) {
4901             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4902                     AAA + j, ONE + i);
4903             if(message[0] == '+' || message[0] == '~') {
4904                 sprintf(message, "%c%c%c+\n",
4905                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4906                         AAA + j, ONE + i);
4907             }
4908             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4909                 message[1] = BOARD_RGHT   - 1 - j + '1';
4910                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4911             }
4912             SendToProgram(message, cps);
4913           }
4914         }
4915       }
4916     
4917       SendToProgram("c\n", cps);
4918       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4919         bp = &boards[moveNum][i][BOARD_LEFT];
4920         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4921           if (((int) *bp != (int) EmptySquare)
4922               && ((int) *bp >= (int) BlackPawn)) {
4923             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4924                     AAA + j, ONE + i);
4925             if(message[0] == '+' || message[0] == '~') {
4926                 sprintf(message, "%c%c%c+\n",
4927                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4928                         AAA + j, ONE + i);
4929             }
4930             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4931                 message[1] = BOARD_RGHT   - 1 - j + '1';
4932                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4933             }
4934             SendToProgram(message, cps);
4935           }
4936         }
4937       }
4938     
4939       SendToProgram(".\n", cps);
4940     }
4941     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4942 }
4943
4944 int
4945 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4946 {
4947     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4948     /* [HGM] add Shogi promotions */
4949     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4950     ChessSquare piece;
4951     ChessMove moveType;
4952     Boolean premove;
4953
4954     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4955     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4956
4957     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4958       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4959         return FALSE;
4960
4961     piece = boards[currentMove][fromY][fromX];
4962     if(gameInfo.variant == VariantShogi) {
4963         promotionZoneSize = 3;
4964         highestPromotingPiece = (int)WhiteFerz;
4965     }
4966
4967     // next weed out all moves that do not touch the promotion zone at all
4968     if((int)piece >= BlackPawn) {
4969         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4970              return FALSE;
4971         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4972     } else {
4973         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4974            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4975     }
4976
4977     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4978
4979     // weed out mandatory Shogi promotions
4980     if(gameInfo.variant == VariantShogi) {
4981         if(piece >= BlackPawn) {
4982             if(toY == 0 && piece == BlackPawn ||
4983                toY == 0 && piece == BlackQueen ||
4984                toY <= 1 && piece == BlackKnight) {
4985                 *promoChoice = '+';
4986                 return FALSE;
4987             }
4988         } else {
4989             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4990                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4991                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4992                 *promoChoice = '+';
4993                 return FALSE;
4994             }
4995         }
4996     }
4997
4998     // weed out obviously illegal Pawn moves
4999     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5000         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5001         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5002         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5003         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5004         // note we are not allowed to test for valid (non-)capture, due to premove
5005     }
5006
5007     // we either have a choice what to promote to, or (in Shogi) whether to promote
5008     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5009         *promoChoice = PieceToChar(BlackFerz);  // no choice
5010         return FALSE;
5011     }
5012     if(appData.alwaysPromoteToQueen) { // predetermined
5013         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5014              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5015         else *promoChoice = PieceToChar(BlackQueen);
5016         return FALSE;
5017     }
5018
5019     // suppress promotion popup on illegal moves that are not premoves
5020     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5021               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5022     if(appData.testLegality && !premove) {
5023         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5024                         fromY, fromX, toY, toX, NULLCHAR);
5025         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5026            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5027             return FALSE;
5028     }
5029
5030     return TRUE;
5031 }
5032
5033 int
5034 InPalace(row, column)
5035      int row, column;
5036 {   /* [HGM] for Xiangqi */
5037     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5038          column < (BOARD_WIDTH + 4)/2 &&
5039          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5040     return FALSE;
5041 }
5042
5043 int
5044 PieceForSquare (x, y)
5045      int x;
5046      int y;
5047 {
5048   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5049      return -1;
5050   else
5051      return boards[currentMove][y][x];
5052 }
5053
5054 int
5055 OKToStartUserMove(x, y)
5056      int x, y;
5057 {
5058     ChessSquare from_piece;
5059     int white_piece;
5060
5061     if (matchMode) return FALSE;
5062     if (gameMode == EditPosition) return TRUE;
5063
5064     if (x >= 0 && y >= 0)
5065       from_piece = boards[currentMove][y][x];
5066     else
5067       from_piece = EmptySquare;
5068
5069     if (from_piece == EmptySquare) return FALSE;
5070
5071     white_piece = (int)from_piece >= (int)WhitePawn &&
5072       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5073
5074     switch (gameMode) {
5075       case PlayFromGameFile:
5076       case AnalyzeFile:
5077       case TwoMachinesPlay:
5078       case EndOfGame:
5079         return FALSE;
5080
5081       case IcsObserving:
5082       case IcsIdle:
5083         return FALSE;
5084
5085       case MachinePlaysWhite:
5086       case IcsPlayingBlack:
5087         if (appData.zippyPlay) return FALSE;
5088         if (white_piece) {
5089             DisplayMoveError(_("You are playing Black"));
5090             return FALSE;
5091         }
5092         break;
5093
5094       case MachinePlaysBlack:
5095       case IcsPlayingWhite:
5096         if (appData.zippyPlay) return FALSE;
5097         if (!white_piece) {
5098             DisplayMoveError(_("You are playing White"));
5099             return FALSE;
5100         }
5101         break;
5102
5103       case EditGame:
5104         if (!white_piece && WhiteOnMove(currentMove)) {
5105             DisplayMoveError(_("It is White's turn"));
5106             return FALSE;
5107         }           
5108         if (white_piece && !WhiteOnMove(currentMove)) {
5109             DisplayMoveError(_("It is Black's turn"));
5110             return FALSE;
5111         }           
5112         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5113             /* Editing correspondence game history */
5114             /* Could disallow this or prompt for confirmation */
5115             cmailOldMove = -1;
5116         }
5117         break;
5118
5119       case BeginningOfGame:
5120         if (appData.icsActive) return FALSE;
5121         if (!appData.noChessProgram) {
5122             if (!white_piece) {
5123                 DisplayMoveError(_("You are playing White"));
5124                 return FALSE;
5125             }
5126         }
5127         break;
5128         
5129       case Training:
5130         if (!white_piece && WhiteOnMove(currentMove)) {
5131             DisplayMoveError(_("It is White's turn"));
5132             return FALSE;
5133         }           
5134         if (white_piece && !WhiteOnMove(currentMove)) {
5135             DisplayMoveError(_("It is Black's turn"));
5136             return FALSE;
5137         }           
5138         break;
5139
5140       default:
5141       case IcsExamining:
5142         break;
5143     }
5144     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5145         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5146         && gameMode != AnalyzeFile && gameMode != Training) {
5147         DisplayMoveError(_("Displayed position is not current"));
5148         return FALSE;
5149     }
5150     return TRUE;
5151 }
5152
5153 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5154 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5155 int lastLoadGameUseList = FALSE;
5156 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5157 ChessMove lastLoadGameStart = (ChessMove) 0;
5158
5159 ChessMove
5160 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5161      int fromX, fromY, toX, toY;
5162      int promoChar;
5163      Boolean captureOwn;
5164 {
5165     ChessMove moveType;
5166     ChessSquare pdown, pup;
5167
5168     /* Check if the user is playing in turn.  This is complicated because we
5169        let the user "pick up" a piece before it is his turn.  So the piece he
5170        tried to pick up may have been captured by the time he puts it down!
5171        Therefore we use the color the user is supposed to be playing in this
5172        test, not the color of the piece that is currently on the starting
5173        square---except in EditGame mode, where the user is playing both
5174        sides; fortunately there the capture race can't happen.  (It can
5175        now happen in IcsExamining mode, but that's just too bad.  The user
5176        will get a somewhat confusing message in that case.)
5177        */
5178
5179     switch (gameMode) {
5180       case PlayFromGameFile:
5181       case AnalyzeFile:
5182       case TwoMachinesPlay:
5183       case EndOfGame:
5184       case IcsObserving:
5185       case IcsIdle:
5186         /* We switched into a game mode where moves are not accepted,
5187            perhaps while the mouse button was down. */
5188         return ImpossibleMove;
5189
5190       case MachinePlaysWhite:
5191         /* User is moving for Black */
5192         if (WhiteOnMove(currentMove)) {
5193             DisplayMoveError(_("It is White's turn"));
5194             return ImpossibleMove;
5195         }
5196         break;
5197
5198       case MachinePlaysBlack:
5199         /* User is moving for White */
5200         if (!WhiteOnMove(currentMove)) {
5201             DisplayMoveError(_("It is Black's turn"));
5202             return ImpossibleMove;
5203         }
5204         break;
5205
5206       case EditGame:
5207       case IcsExamining:
5208       case BeginningOfGame:
5209       case AnalyzeMode:
5210       case Training:
5211         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5212             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5213             /* User is moving for Black */
5214             if (WhiteOnMove(currentMove)) {
5215                 DisplayMoveError(_("It is White's turn"));
5216                 return ImpossibleMove;
5217             }
5218         } else {
5219             /* User is moving for White */
5220             if (!WhiteOnMove(currentMove)) {
5221                 DisplayMoveError(_("It is Black's turn"));
5222                 return ImpossibleMove;
5223             }
5224         }
5225         break;
5226
5227       case IcsPlayingBlack:
5228         /* User is moving for Black */
5229         if (WhiteOnMove(currentMove)) {
5230             if (!appData.premove) {
5231                 DisplayMoveError(_("It is White's turn"));
5232             } else if (toX >= 0 && toY >= 0) {
5233                 premoveToX = toX;
5234                 premoveToY = toY;
5235                 premoveFromX = fromX;
5236                 premoveFromY = fromY;
5237                 premovePromoChar = promoChar;
5238                 gotPremove = 1;
5239                 if (appData.debugMode) 
5240                     fprintf(debugFP, "Got premove: fromX %d,"
5241                             "fromY %d, toX %d, toY %d\n",
5242                             fromX, fromY, toX, toY);
5243             }
5244             return ImpossibleMove;
5245         }
5246         break;
5247
5248       case IcsPlayingWhite:
5249         /* User is moving for White */
5250         if (!WhiteOnMove(currentMove)) {
5251             if (!appData.premove) {
5252                 DisplayMoveError(_("It is Black's turn"));
5253             } else if (toX >= 0 && toY >= 0) {
5254                 premoveToX = toX;
5255                 premoveToY = toY;
5256                 premoveFromX = fromX;
5257                 premoveFromY = fromY;
5258                 premovePromoChar = promoChar;
5259                 gotPremove = 1;
5260                 if (appData.debugMode) 
5261                     fprintf(debugFP, "Got premove: fromX %d,"
5262                             "fromY %d, toX %d, toY %d\n",
5263                             fromX, fromY, toX, toY);
5264             }
5265             return ImpossibleMove;
5266         }
5267         break;
5268
5269       default:
5270         break;
5271
5272       case EditPosition:
5273         /* EditPosition, empty square, or different color piece;
5274            click-click move is possible */
5275         if (toX == -2 || toY == -2) {
5276             boards[0][fromY][fromX] = EmptySquare;
5277             return AmbiguousMove;
5278         } else if (toX >= 0 && toY >= 0) {
5279             boards[0][toY][toX] = boards[0][fromY][fromX];
5280             boards[0][fromY][fromX] = EmptySquare;
5281             return AmbiguousMove;
5282         }
5283         return ImpossibleMove;
5284     }
5285
5286     if(toX < 0 || toY < 0) return ImpossibleMove;
5287     pdown = boards[currentMove][fromY][fromX];
5288     pup = boards[currentMove][toY][toX];
5289
5290     /* [HGM] If move started in holdings, it means a drop */
5291     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5292          if( pup != EmptySquare ) return ImpossibleMove;
5293          if(appData.testLegality) {
5294              /* it would be more logical if LegalityTest() also figured out
5295               * which drops are legal. For now we forbid pawns on back rank.
5296               * Shogi is on its own here...
5297               */
5298              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5299                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5300                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5301          }
5302          return WhiteDrop; /* Not needed to specify white or black yet */
5303     }
5304
5305     userOfferedDraw = FALSE;
5306         
5307     /* [HGM] always test for legality, to get promotion info */
5308     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5309                                          fromY, fromX, toY, toX, promoChar);
5310     /* [HGM] but possibly ignore an IllegalMove result */
5311     if (appData.testLegality) {
5312         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5313             DisplayMoveError(_("Illegal move"));
5314             return ImpossibleMove;
5315         }
5316     }
5317
5318     return moveType;
5319     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5320        function is made into one that returns an OK move type if FinishMove
5321        should be called. This to give the calling driver routine the
5322        opportunity to finish the userMove input with a promotion popup,
5323        without bothering the user with this for invalid or illegal moves */
5324
5325 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5326 }
5327
5328 /* Common tail of UserMoveEvent and DropMenuEvent */
5329 int
5330 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5331      ChessMove moveType;
5332      int fromX, fromY, toX, toY;
5333      /*char*/int promoChar;
5334 {
5335     char *bookHit = 0;
5336
5337     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5338         // [HGM] superchess: suppress promotions to non-available piece
5339         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5340         if(WhiteOnMove(currentMove)) {
5341             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5342         } else {
5343             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5344         }
5345     }
5346
5347     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5348        move type in caller when we know the move is a legal promotion */
5349     if(moveType == NormalMove && promoChar)
5350         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5351
5352     /* [HGM] convert drag-and-drop piece drops to standard form */
5353     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5354          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5355            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5356                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5357            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5358            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5359            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5360            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5361          fromY = DROP_RANK;
5362     }
5363
5364     /* [HGM] <popupFix> The following if has been moved here from
5365        UserMoveEvent(). Because it seemed to belong here (why not allow
5366        piece drops in training games?), and because it can only be
5367        performed after it is known to what we promote. */
5368     if (gameMode == Training) {
5369       /* compare the move played on the board to the next move in the
5370        * game. If they match, display the move and the opponent's response. 
5371        * If they don't match, display an error message.
5372        */
5373       int saveAnimate;
5374       Board testBoard;
5375       CopyBoard(testBoard, boards[currentMove]);
5376       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5377
5378       if (CompareBoards(testBoard, boards[currentMove+1])) {
5379         ForwardInner(currentMove+1);
5380
5381         /* Autoplay the opponent's response.
5382          * if appData.animate was TRUE when Training mode was entered,
5383          * the response will be animated.
5384          */
5385         saveAnimate = appData.animate;
5386         appData.animate = animateTraining;
5387         ForwardInner(currentMove+1);
5388         appData.animate = saveAnimate;
5389
5390         /* check for the end of the game */
5391         if (currentMove >= forwardMostMove) {
5392           gameMode = PlayFromGameFile;
5393           ModeHighlight();
5394           SetTrainingModeOff();
5395           DisplayInformation(_("End of game"));
5396         }
5397       } else {
5398         DisplayError(_("Incorrect move"), 0);
5399       }
5400       return 1;
5401     }
5402
5403   /* Ok, now we know that the move is good, so we can kill
5404      the previous line in Analysis Mode */
5405   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5406                                 && currentMove < forwardMostMove) {
5407     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5408   }
5409
5410   /* If we need the chess program but it's dead, restart it */
5411   ResurrectChessProgram();
5412
5413   /* A user move restarts a paused game*/
5414   if (pausing)
5415     PauseEvent();
5416
5417   thinkOutput[0] = NULLCHAR;
5418
5419   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5420
5421   if (gameMode == BeginningOfGame) {
5422     if (appData.noChessProgram) {
5423       gameMode = EditGame;
5424       SetGameInfo();
5425     } else {
5426       char buf[MSG_SIZ];
5427       gameMode = MachinePlaysBlack;
5428       StartClocks();
5429       SetGameInfo();
5430       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5431       DisplayTitle(buf);
5432       if (first.sendName) {
5433         sprintf(buf, "name %s\n", gameInfo.white);
5434         SendToProgram(buf, &first);
5435       }
5436       StartClocks();
5437     }
5438     ModeHighlight();
5439   }
5440
5441   /* Relay move to ICS or chess engine */
5442   if (appData.icsActive) {
5443     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5444         gameMode == IcsExamining) {
5445       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5446       ics_user_moved = 1;
5447     }
5448   } else {
5449     if (first.sendTime && (gameMode == BeginningOfGame ||
5450                            gameMode == MachinePlaysWhite ||
5451                            gameMode == MachinePlaysBlack)) {
5452       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5453     }
5454     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5455          // [HGM] book: if program might be playing, let it use book
5456         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5457         first.maybeThinking = TRUE;
5458     } else SendMoveToProgram(forwardMostMove-1, &first);
5459     if (currentMove == cmailOldMove + 1) {
5460       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5461     }
5462   }
5463
5464   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5465
5466   switch (gameMode) {
5467   case EditGame:
5468     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5469     case MT_NONE:
5470     case MT_CHECK:
5471       break;
5472     case MT_CHECKMATE:
5473     case MT_STAINMATE:
5474       if (WhiteOnMove(currentMove)) {
5475         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5476       } else {
5477         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5478       }
5479       break;
5480     case MT_STALEMATE:
5481       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5482       break;
5483     }
5484     break;
5485     
5486   case MachinePlaysBlack:
5487   case MachinePlaysWhite:
5488     /* disable certain menu options while machine is thinking */
5489     SetMachineThinkingEnables();
5490     break;
5491
5492   default:
5493     break;
5494   }
5495
5496   if(bookHit) { // [HGM] book: simulate book reply
5497         static char bookMove[MSG_SIZ]; // a bit generous?
5498
5499         programStats.nodes = programStats.depth = programStats.time = 
5500         programStats.score = programStats.got_only_move = 0;
5501         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5502
5503         strcpy(bookMove, "move ");
5504         strcat(bookMove, bookHit);
5505         HandleMachineMove(bookMove, &first);
5506   }
5507   return 1;
5508 }
5509
5510 void
5511 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5512      int fromX, fromY, toX, toY;
5513      int promoChar;
5514 {
5515     /* [HGM] This routine was added to allow calling of its two logical
5516        parts from other modules in the old way. Before, UserMoveEvent()
5517        automatically called FinishMove() if the move was OK, and returned
5518        otherwise. I separated the two, in order to make it possible to
5519        slip a promotion popup in between. But that it always needs two
5520        calls, to the first part, (now called UserMoveTest() ), and to
5521        FinishMove if the first part succeeded. Calls that do not need
5522        to do anything in between, can call this routine the old way. 
5523     */
5524     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5525 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5526     if(moveType == AmbiguousMove)
5527         DrawPosition(FALSE, boards[currentMove]);
5528     else if(moveType != ImpossibleMove && moveType != Comment)
5529         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5530 }
5531
5532 void LeftClick(ClickType clickType, int xPix, int yPix)
5533 {
5534     int x, y;
5535     Boolean saveAnimate;
5536     static int second = 0, promotionChoice = 0;
5537     char promoChoice = NULLCHAR;
5538
5539     if (clickType == Press) ErrorPopDown();
5540
5541     x = EventToSquare(xPix, BOARD_WIDTH);
5542     y = EventToSquare(yPix, BOARD_HEIGHT);
5543     if (!flipView && y >= 0) {
5544         y = BOARD_HEIGHT - 1 - y;
5545     }
5546     if (flipView && x >= 0) {
5547         x = BOARD_WIDTH - 1 - x;
5548     }
5549
5550     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5551         if(clickType == Release) return; // ignore upclick of click-click destination
5552         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5553         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5554         if(gameInfo.holdingsWidth && 
5555                 (WhiteOnMove(currentMove) 
5556                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5557                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5558             // click in right holdings, for determining promotion piece
5559             ChessSquare p = boards[currentMove][y][x];
5560             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5561             if(p != EmptySquare) {
5562                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5563                 fromX = fromY = -1;
5564                 return;
5565             }
5566         }
5567         DrawPosition(FALSE, boards[currentMove]);
5568         return;
5569     }
5570
5571     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5572     if(clickType == Press
5573             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5574               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5575               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5576         return;
5577
5578     if (fromX == -1) {
5579         if (clickType == Press) {
5580             /* First square */
5581             if (OKToStartUserMove(x, y)) {
5582                 fromX = x;
5583                 fromY = y;
5584                 second = 0;
5585                 DragPieceBegin(xPix, yPix);
5586                 if (appData.highlightDragging) {
5587                     SetHighlights(x, y, -1, -1);
5588                 }
5589             }
5590         }
5591         return;
5592     }
5593
5594     /* fromX != -1 */
5595     if (clickType == Press && gameMode != EditPosition) {
5596         ChessSquare fromP;
5597         ChessSquare toP;
5598         int frc;
5599
5600         // ignore off-board to clicks
5601         if(y < 0 || x < 0) return;
5602
5603         /* Check if clicking again on the same color piece */
5604         fromP = boards[currentMove][fromY][fromX];
5605         toP = boards[currentMove][y][x];
5606         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5607         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5608              WhitePawn <= toP && toP <= WhiteKing &&
5609              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5610              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5611             (BlackPawn <= fromP && fromP <= BlackKing && 
5612              BlackPawn <= toP && toP <= BlackKing &&
5613              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5614              !(fromP == BlackKing && toP == BlackRook && frc))) {
5615             /* Clicked again on same color piece -- changed his mind */
5616             second = (x == fromX && y == fromY);
5617             if (appData.highlightDragging) {
5618                 SetHighlights(x, y, -1, -1);
5619             } else {
5620                 ClearHighlights();
5621             }
5622             if (OKToStartUserMove(x, y)) {
5623                 fromX = x;
5624                 fromY = y;
5625                 DragPieceBegin(xPix, yPix);
5626             }
5627             return;
5628         }
5629         // ignore clicks on holdings
5630         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5631     }
5632
5633     if (clickType == Release && x == fromX && y == fromY) {
5634         DragPieceEnd(xPix, yPix);
5635         if (appData.animateDragging) {
5636             /* Undo animation damage if any */
5637             DrawPosition(FALSE, NULL);
5638         }
5639         if (second) {
5640             /* Second up/down in same square; just abort move */
5641             second = 0;
5642             fromX = fromY = -1;
5643             ClearHighlights();
5644             gotPremove = 0;
5645             ClearPremoveHighlights();
5646         } else {
5647             /* First upclick in same square; start click-click mode */
5648             SetHighlights(x, y, -1, -1);
5649         }
5650         return;
5651     }
5652
5653     /* we now have a different from- and (possibly off-board) to-square */
5654     /* Completed move */
5655     toX = x;
5656     toY = y;
5657     saveAnimate = appData.animate;
5658     if (clickType == Press) {
5659         /* Finish clickclick move */
5660         if (appData.animate || appData.highlightLastMove) {
5661             SetHighlights(fromX, fromY, toX, toY);
5662         } else {
5663             ClearHighlights();
5664         }
5665     } else {
5666         /* Finish drag move */
5667         if (appData.highlightLastMove) {
5668             SetHighlights(fromX, fromY, toX, toY);
5669         } else {
5670             ClearHighlights();
5671         }
5672         DragPieceEnd(xPix, yPix);
5673         /* Don't animate move and drag both */
5674         appData.animate = FALSE;
5675     }
5676
5677     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5678     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5679         ClearHighlights();
5680         fromX = fromY = -1;
5681         DrawPosition(TRUE, NULL);
5682         return;
5683     }
5684
5685     // off-board moves should not be highlighted
5686     if(x < 0 || x < 0) ClearHighlights();
5687
5688     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5689         SetHighlights(fromX, fromY, toX, toY);
5690         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5691             // [HGM] super: promotion to captured piece selected from holdings
5692             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5693             promotionChoice = TRUE;
5694             // kludge follows to temporarily execute move on display, without promoting yet
5695             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5696             boards[currentMove][toY][toX] = p;
5697             DrawPosition(FALSE, boards[currentMove]);
5698             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5699             boards[currentMove][toY][toX] = q;
5700             DisplayMessage("Click in holdings to choose piece", "");
5701             return;
5702         }
5703         PromotionPopUp();
5704     } else {
5705         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5706         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5707         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5708         fromX = fromY = -1;
5709     }
5710     appData.animate = saveAnimate;
5711     if (appData.animate || appData.animateDragging) {
5712         /* Undo animation damage if needed */
5713         DrawPosition(FALSE, NULL);
5714     }
5715 }
5716
5717 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5718 {
5719 //    char * hint = lastHint;
5720     FrontEndProgramStats stats;
5721
5722     stats.which = cps == &first ? 0 : 1;
5723     stats.depth = cpstats->depth;
5724     stats.nodes = cpstats->nodes;
5725     stats.score = cpstats->score;
5726     stats.time = cpstats->time;
5727     stats.pv = cpstats->movelist;
5728     stats.hint = lastHint;
5729     stats.an_move_index = 0;
5730     stats.an_move_count = 0;
5731
5732     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5733         stats.hint = cpstats->move_name;
5734         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5735         stats.an_move_count = cpstats->nr_moves;
5736     }
5737
5738     SetProgramStats( &stats );
5739 }
5740
5741 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5742 {   // [HGM] book: this routine intercepts moves to simulate book replies
5743     char *bookHit = NULL;
5744
5745     //first determine if the incoming move brings opponent into his book
5746     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5747         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5748     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5749     if(bookHit != NULL && !cps->bookSuspend) {
5750         // make sure opponent is not going to reply after receiving move to book position
5751         SendToProgram("force\n", cps);
5752         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5753     }
5754     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5755     // now arrange restart after book miss
5756     if(bookHit) {
5757         // after a book hit we never send 'go', and the code after the call to this routine
5758         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5759         char buf[MSG_SIZ];
5760         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5761         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5762         SendToProgram(buf, cps);
5763         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5764     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5765         SendToProgram("go\n", cps);
5766         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5767     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5768         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5769             SendToProgram("go\n", cps); 
5770         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5771     }
5772     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5773 }
5774
5775 char *savedMessage;
5776 ChessProgramState *savedState;
5777 void DeferredBookMove(void)
5778 {
5779         if(savedState->lastPing != savedState->lastPong)
5780                     ScheduleDelayedEvent(DeferredBookMove, 10);
5781         else
5782         HandleMachineMove(savedMessage, savedState);
5783 }
5784
5785 void
5786 HandleMachineMove(message, cps)
5787      char *message;
5788      ChessProgramState *cps;
5789 {
5790     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5791     char realname[MSG_SIZ];
5792     int fromX, fromY, toX, toY;
5793     ChessMove moveType;
5794     char promoChar;
5795     char *p;
5796     int machineWhite;
5797     char *bookHit;
5798
5799 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5800     /*
5801      * Kludge to ignore BEL characters
5802      */
5803     while (*message == '\007') message++;
5804
5805     /*
5806      * [HGM] engine debug message: ignore lines starting with '#' character
5807      */
5808     if(cps->debug && *message == '#') return;
5809
5810     /*
5811      * Look for book output
5812      */
5813     if (cps == &first && bookRequested) {
5814         if (message[0] == '\t' || message[0] == ' ') {
5815             /* Part of the book output is here; append it */
5816             strcat(bookOutput, message);
5817             strcat(bookOutput, "  \n");
5818             return;
5819         } else if (bookOutput[0] != NULLCHAR) {
5820             /* All of book output has arrived; display it */
5821             char *p = bookOutput;
5822             while (*p != NULLCHAR) {
5823                 if (*p == '\t') *p = ' ';
5824                 p++;
5825             }
5826             DisplayInformation(bookOutput);
5827             bookRequested = FALSE;
5828             /* Fall through to parse the current output */
5829         }
5830     }
5831
5832     /*
5833      * Look for machine move.
5834      */
5835     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5836         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5837     {
5838         /* This method is only useful on engines that support ping */
5839         if (cps->lastPing != cps->lastPong) {
5840           if (gameMode == BeginningOfGame) {
5841             /* Extra move from before last new; ignore */
5842             if (appData.debugMode) {
5843                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5844             }
5845           } else {
5846             if (appData.debugMode) {
5847                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5848                         cps->which, gameMode);
5849             }
5850
5851             SendToProgram("undo\n", cps);
5852           }
5853           return;
5854         }
5855
5856         switch (gameMode) {
5857           case BeginningOfGame:
5858             /* Extra move from before last reset; ignore */
5859             if (appData.debugMode) {
5860                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5861             }
5862             return;
5863
5864           case EndOfGame:
5865           case IcsIdle:
5866           default:
5867             /* Extra move after we tried to stop.  The mode test is
5868                not a reliable way of detecting this problem, but it's
5869                the best we can do on engines that don't support ping.
5870             */
5871             if (appData.debugMode) {
5872                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5873                         cps->which, gameMode);
5874             }
5875             SendToProgram("undo\n", cps);
5876             return;
5877
5878           case MachinePlaysWhite:
5879           case IcsPlayingWhite:
5880             machineWhite = TRUE;
5881             break;
5882
5883           case MachinePlaysBlack:
5884           case IcsPlayingBlack:
5885             machineWhite = FALSE;
5886             break;
5887
5888           case TwoMachinesPlay:
5889             machineWhite = (cps->twoMachinesColor[0] == 'w');
5890             break;
5891         }
5892         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5893             if (appData.debugMode) {
5894                 fprintf(debugFP,
5895                         "Ignoring move out of turn by %s, gameMode %d"
5896                         ", forwardMost %d\n",
5897                         cps->which, gameMode, forwardMostMove);
5898             }
5899             return;
5900         }
5901
5902     if (appData.debugMode) { int f = forwardMostMove;
5903         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5904                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5905                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5906     }
5907         if(cps->alphaRank) AlphaRank(machineMove, 4);
5908         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5909                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5910             /* Machine move could not be parsed; ignore it. */
5911             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5912                     machineMove, cps->which);
5913             DisplayError(buf1, 0);
5914             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5915                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5916             if (gameMode == TwoMachinesPlay) {
5917               GameEnds(machineWhite ? BlackWins : WhiteWins,
5918                        buf1, GE_XBOARD);
5919             }
5920             return;
5921         }
5922
5923         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5924         /* So we have to redo legality test with true e.p. status here,  */
5925         /* to make sure an illegal e.p. capture does not slip through,   */
5926         /* to cause a forfeit on a justified illegal-move complaint      */
5927         /* of the opponent.                                              */
5928         if( gameMode==TwoMachinesPlay && appData.testLegality
5929             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5930                                                               ) {
5931            ChessMove moveType;
5932            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5933                              fromY, fromX, toY, toX, promoChar);
5934             if (appData.debugMode) {
5935                 int i;
5936                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5937                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5938                 fprintf(debugFP, "castling rights\n");
5939             }
5940             if(moveType == IllegalMove) {
5941                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5942                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5943                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5944                            buf1, GE_XBOARD);
5945                 return;
5946            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5947            /* [HGM] Kludge to handle engines that send FRC-style castling
5948               when they shouldn't (like TSCP-Gothic) */
5949            switch(moveType) {
5950              case WhiteASideCastleFR:
5951              case BlackASideCastleFR:
5952                toX+=2;
5953                currentMoveString[2]++;
5954                break;
5955              case WhiteHSideCastleFR:
5956              case BlackHSideCastleFR:
5957                toX--;
5958                currentMoveString[2]--;
5959                break;
5960              default: ; // nothing to do, but suppresses warning of pedantic compilers
5961            }
5962         }
5963         hintRequested = FALSE;
5964         lastHint[0] = NULLCHAR;
5965         bookRequested = FALSE;
5966         /* Program may be pondering now */
5967         cps->maybeThinking = TRUE;
5968         if (cps->sendTime == 2) cps->sendTime = 1;
5969         if (cps->offeredDraw) cps->offeredDraw--;
5970
5971 #if ZIPPY
5972         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5973             first.initDone) {
5974           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5975           ics_user_moved = 1;
5976           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5977                 char buf[3*MSG_SIZ];
5978
5979                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5980                         programStats.score / 100.,
5981                         programStats.depth,
5982                         programStats.time / 100.,
5983                         (unsigned int)programStats.nodes,
5984                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5985                         programStats.movelist);
5986                 SendToICS(buf);
5987 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5988           }
5989         }
5990 #endif
5991         /* currentMoveString is set as a side-effect of ParseOneMove */
5992         strcpy(machineMove, currentMoveString);
5993         strcat(machineMove, "\n");
5994         strcpy(moveList[forwardMostMove], machineMove);
5995
5996         /* [AS] Save move info and clear stats for next move */
5997         pvInfoList[ forwardMostMove ].score = programStats.score;
5998         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5999         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6000         ClearProgramStats();
6001         thinkOutput[0] = NULLCHAR;
6002         hiddenThinkOutputState = 0;
6003
6004         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6005
6006         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6007         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6008             int count = 0;
6009
6010             while( count < adjudicateLossPlies ) {
6011                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6012
6013                 if( count & 1 ) {
6014                     score = -score; /* Flip score for winning side */
6015                 }
6016
6017                 if( score > adjudicateLossThreshold ) {
6018                     break;
6019                 }
6020
6021                 count++;
6022             }
6023
6024             if( count >= adjudicateLossPlies ) {
6025                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6026
6027                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6028                     "Xboard adjudication", 
6029                     GE_XBOARD );
6030
6031                 return;
6032             }
6033         }
6034
6035         if( gameMode == TwoMachinesPlay ) {
6036           // [HGM] some adjudications useful with buggy engines
6037             int k, count = 0; static int bare = 1;
6038           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6039
6040
6041             if( appData.testLegality )
6042             {   /* [HGM] Some more adjudications for obstinate engines */
6043                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6044                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6045                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6046                 static int moveCount = 6;
6047                 ChessMove result;
6048                 char *reason = NULL;
6049
6050                 /* Count what is on board. */
6051                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6052                 {   ChessSquare p = boards[forwardMostMove][i][j];
6053                     int m=i;
6054
6055                     switch((int) p)
6056                     {   /* count B,N,R and other of each side */
6057                         case WhiteKing:
6058                         case BlackKing:
6059                              NrK++; break; // [HGM] atomic: count Kings
6060                         case WhiteKnight:
6061                              NrWN++; break;
6062                         case WhiteBishop:
6063                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6064                              bishopsColor |= 1 << ((i^j)&1);
6065                              NrWB++; break;
6066                         case BlackKnight:
6067                              NrBN++; break;
6068                         case BlackBishop:
6069                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6070                              bishopsColor |= 1 << ((i^j)&1);
6071                              NrBB++; break;
6072                         case WhiteRook:
6073                              NrWR++; break;
6074                         case BlackRook:
6075                              NrBR++; break;
6076                         case WhiteQueen:
6077                              NrWQ++; break;
6078                         case BlackQueen:
6079                              NrBQ++; break;
6080                         case EmptySquare: 
6081                              break;
6082                         case BlackPawn:
6083                              m = 7-i;
6084                         case WhitePawn:
6085                              PawnAdvance += m; NrPawns++;
6086                     }
6087                     NrPieces += (p != EmptySquare);
6088                     NrW += ((int)p < (int)BlackPawn);
6089                     if(gameInfo.variant == VariantXiangqi && 
6090                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6091                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6092                         NrW -= ((int)p < (int)BlackPawn);
6093                     }
6094                 }
6095
6096                 /* Some material-based adjudications that have to be made before stalemate test */
6097                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6098                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6099                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6100                      if(appData.checkMates) {
6101                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6102                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6103                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6104                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6105                          return;
6106                      }
6107                 }
6108
6109                 /* Bare King in Shatranj (loses) or Losers (wins) */
6110                 if( NrW == 1 || NrPieces - NrW == 1) {
6111                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6112                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6113                      if(appData.checkMates) {
6114                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6115                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6116                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6117                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6118                          return;
6119                      }
6120                   } else
6121                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6122                   {    /* bare King */
6123                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6124                         if(appData.checkMates) {
6125                             /* but only adjudicate if adjudication enabled */
6126                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6127                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6128                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6129                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6130                             return;
6131                         }
6132                   }
6133                 } else bare = 1;
6134
6135
6136             // don't wait for engine to announce game end if we can judge ourselves
6137             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6138               case MT_CHECK:
6139                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6140                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6141                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6142                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6143                             checkCnt++;
6144                         if(checkCnt >= 2) {
6145                             reason = "Xboard adjudication: 3rd check";
6146                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6147                             break;
6148                         }
6149                     }
6150                 }
6151               case MT_NONE:
6152               default:
6153                 break;
6154               case MT_STALEMATE:
6155               case MT_STAINMATE:
6156                 reason = "Xboard adjudication: Stalemate";
6157                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6158                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6159                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6160                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6161                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6162                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6163                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6164                                                                         EP_CHECKMATE : EP_WINS);
6165                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6166                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6167                 }
6168                 break;
6169               case MT_CHECKMATE:
6170                 reason = "Xboard adjudication: Checkmate";
6171                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6172                 break;
6173             }
6174
6175                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6176                     case EP_STALEMATE:
6177                         result = GameIsDrawn; break;
6178                     case EP_CHECKMATE:
6179                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6180                     case EP_WINS:
6181                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6182                     default:
6183                         result = (ChessMove) 0;
6184                 }
6185                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6186                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6187                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6188                     GameEnds( result, reason, GE_XBOARD );
6189                     return;
6190                 }
6191
6192                 /* Next absolutely insufficient mating material. */
6193                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6194                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6195                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6196                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6197                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6198
6199                      /* always flag draws, for judging claims */
6200                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6201
6202                      if(appData.materialDraws) {
6203                          /* but only adjudicate them if adjudication enabled */
6204                          SendToProgram("force\n", cps->other); // suppress reply
6205                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6206                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6207                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6208                          return;
6209                      }
6210                 }
6211
6212                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6213                 if(NrPieces == 4 && 
6214                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6215                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6216                    || NrWN==2 || NrBN==2     /* KNNK */
6217                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6218                   ) ) {
6219                      if(--moveCount < 0 && appData.trivialDraws)
6220                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6221                           SendToProgram("force\n", cps->other); // suppress reply
6222                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6223                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6224                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6225                           return;
6226                      }
6227                 } else moveCount = 6;
6228             }
6229           }
6230           
6231           if (appData.debugMode) { int i;
6232             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6233                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6234                     appData.drawRepeats);
6235             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6236               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6237             
6238           }
6239
6240                 /* Check for rep-draws */
6241                 count = 0;
6242                 for(k = forwardMostMove-2;
6243                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6244                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6245                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6246                     k-=2)
6247                 {   int rights=0;
6248                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6249                         /* compare castling rights */
6250                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6251                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6252                                 rights++; /* King lost rights, while rook still had them */
6253                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6254                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6255                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6256                                    rights++; /* but at least one rook lost them */
6257                         }
6258                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6259                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6260                                 rights++; 
6261                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6262                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6263                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6264                                    rights++;
6265                         }
6266                         if( rights == 0 && ++count > appData.drawRepeats-2
6267                             && appData.drawRepeats > 1) {
6268                              /* adjudicate after user-specified nr of repeats */
6269                              SendToProgram("force\n", cps->other); // suppress reply
6270                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6271                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6272                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6273                                 // [HGM] xiangqi: check for forbidden perpetuals
6274                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6275                                 for(m=forwardMostMove; m>k; m-=2) {
6276                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6277                                         ourPerpetual = 0; // the current mover did not always check
6278                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6279                                         hisPerpetual = 0; // the opponent did not always check
6280                                 }
6281                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6282                                                                         ourPerpetual, hisPerpetual);
6283                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6284                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6285                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6286                                     return;
6287                                 }
6288                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6289                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6290                                 // Now check for perpetual chases
6291                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6292                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6293                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6294                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6295                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6296                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6297                                         return;
6298                                     }
6299                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6300                                         break; // Abort repetition-checking loop.
6301                                 }
6302                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6303                              }
6304                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6305                              return;
6306                         }
6307                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6308                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6309                     }
6310                 }
6311
6312                 /* Now we test for 50-move draws. Determine ply count */
6313                 count = forwardMostMove;
6314                 /* look for last irreversble move */
6315                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6316                     count--;
6317                 /* if we hit starting position, add initial plies */
6318                 if( count == backwardMostMove )
6319                     count -= initialRulePlies;
6320                 count = forwardMostMove - count; 
6321                 if( count >= 100)
6322                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6323                          /* this is used to judge if draw claims are legal */
6324                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6325                          SendToProgram("force\n", cps->other); // suppress reply
6326                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6327                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6328                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6329                          return;
6330                 }
6331
6332                 /* if draw offer is pending, treat it as a draw claim
6333                  * when draw condition present, to allow engines a way to
6334                  * claim draws before making their move to avoid a race
6335                  * condition occurring after their move
6336                  */
6337                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6338                          char *p = NULL;
6339                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6340                              p = "Draw claim: 50-move rule";
6341                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6342                              p = "Draw claim: 3-fold repetition";
6343                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6344                              p = "Draw claim: insufficient mating material";
6345                          if( p != NULL ) {
6346                              SendToProgram("force\n", cps->other); // suppress reply
6347                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6348                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6349                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6350                              return;
6351                          }
6352                 }
6353
6354
6355                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6356                     SendToProgram("force\n", cps->other); // suppress reply
6357                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6358                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6359
6360                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6361
6362                     return;
6363                 }
6364         }
6365
6366         bookHit = NULL;
6367         if (gameMode == TwoMachinesPlay) {
6368             /* [HGM] relaying draw offers moved to after reception of move */
6369             /* and interpreting offer as claim if it brings draw condition */
6370             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6371                 SendToProgram("draw\n", cps->other);
6372             }
6373             if (cps->other->sendTime) {
6374                 SendTimeRemaining(cps->other,
6375                                   cps->other->twoMachinesColor[0] == 'w');
6376             }
6377             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6378             if (firstMove && !bookHit) {
6379                 firstMove = FALSE;
6380                 if (cps->other->useColors) {
6381                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6382                 }
6383                 SendToProgram("go\n", cps->other);
6384             }
6385             cps->other->maybeThinking = TRUE;
6386         }
6387
6388         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6389         
6390         if (!pausing && appData.ringBellAfterMoves) {
6391             RingBell();
6392         }
6393
6394         /* 
6395          * Reenable menu items that were disabled while
6396          * machine was thinking
6397          */
6398         if (gameMode != TwoMachinesPlay)
6399             SetUserThinkingEnables();
6400
6401         // [HGM] book: after book hit opponent has received move and is now in force mode
6402         // force the book reply into it, and then fake that it outputted this move by jumping
6403         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6404         if(bookHit) {
6405                 static char bookMove[MSG_SIZ]; // a bit generous?
6406
6407                 strcpy(bookMove, "move ");
6408                 strcat(bookMove, bookHit);
6409                 message = bookMove;
6410                 cps = cps->other;
6411                 programStats.nodes = programStats.depth = programStats.time = 
6412                 programStats.score = programStats.got_only_move = 0;
6413                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6414
6415                 if(cps->lastPing != cps->lastPong) {
6416                     savedMessage = message; // args for deferred call
6417                     savedState = cps;
6418                     ScheduleDelayedEvent(DeferredBookMove, 10);
6419                     return;
6420                 }
6421                 goto FakeBookMove;
6422         }
6423
6424         return;
6425     }
6426
6427     /* Set special modes for chess engines.  Later something general
6428      *  could be added here; for now there is just one kludge feature,
6429      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6430      *  when "xboard" is given as an interactive command.
6431      */
6432     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6433         cps->useSigint = FALSE;
6434         cps->useSigterm = FALSE;
6435     }
6436     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6437       ParseFeatures(message+8, cps);
6438       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6439     }
6440
6441     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6442      * want this, I was asked to put it in, and obliged.
6443      */
6444     if (!strncmp(message, "setboard ", 9)) {
6445         Board initial_position;
6446
6447         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6448
6449         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6450             DisplayError(_("Bad FEN received from engine"), 0);
6451             return ;
6452         } else {
6453            Reset(TRUE, FALSE);
6454            CopyBoard(boards[0], initial_position);
6455            initialRulePlies = FENrulePlies;
6456            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6457            else gameMode = MachinePlaysBlack;                 
6458            DrawPosition(FALSE, boards[currentMove]);
6459         }
6460         return;
6461     }
6462
6463     /*
6464      * Look for communication commands
6465      */
6466     if (!strncmp(message, "telluser ", 9)) {
6467         DisplayNote(message + 9);
6468         return;
6469     }
6470     if (!strncmp(message, "tellusererror ", 14)) {
6471         DisplayError(message + 14, 0);
6472         return;
6473     }
6474     if (!strncmp(message, "tellopponent ", 13)) {
6475       if (appData.icsActive) {
6476         if (loggedOn) {
6477           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6478           SendToICS(buf1);
6479         }
6480       } else {
6481         DisplayNote(message + 13);
6482       }
6483       return;
6484     }
6485     if (!strncmp(message, "tellothers ", 11)) {
6486       if (appData.icsActive) {
6487         if (loggedOn) {
6488           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6489           SendToICS(buf1);
6490         }
6491       }
6492       return;
6493     }
6494     if (!strncmp(message, "tellall ", 8)) {
6495       if (appData.icsActive) {
6496         if (loggedOn) {
6497           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6498           SendToICS(buf1);
6499         }
6500       } else {
6501         DisplayNote(message + 8);
6502       }
6503       return;
6504     }
6505     if (strncmp(message, "warning", 7) == 0) {
6506         /* Undocumented feature, use tellusererror in new code */
6507         DisplayError(message, 0);
6508         return;
6509     }
6510     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6511         strcpy(realname, cps->tidy);
6512         strcat(realname, " query");
6513         AskQuestion(realname, buf2, buf1, cps->pr);
6514         return;
6515     }
6516     /* Commands from the engine directly to ICS.  We don't allow these to be 
6517      *  sent until we are logged on. Crafty kibitzes have been known to 
6518      *  interfere with the login process.
6519      */
6520     if (loggedOn) {
6521         if (!strncmp(message, "tellics ", 8)) {
6522             SendToICS(message + 8);
6523             SendToICS("\n");
6524             return;
6525         }
6526         if (!strncmp(message, "tellicsnoalias ", 15)) {
6527             SendToICS(ics_prefix);
6528             SendToICS(message + 15);
6529             SendToICS("\n");
6530             return;
6531         }
6532         /* The following are for backward compatibility only */
6533         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6534             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6535             SendToICS(ics_prefix);
6536             SendToICS(message);
6537             SendToICS("\n");
6538             return;
6539         }
6540     }
6541     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6542         return;
6543     }
6544     /*
6545      * If the move is illegal, cancel it and redraw the board.
6546      * Also deal with other error cases.  Matching is rather loose
6547      * here to accommodate engines written before the spec.
6548      */
6549     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6550         strncmp(message, "Error", 5) == 0) {
6551         if (StrStr(message, "name") || 
6552             StrStr(message, "rating") || StrStr(message, "?") ||
6553             StrStr(message, "result") || StrStr(message, "board") ||
6554             StrStr(message, "bk") || StrStr(message, "computer") ||
6555             StrStr(message, "variant") || StrStr(message, "hint") ||
6556             StrStr(message, "random") || StrStr(message, "depth") ||
6557             StrStr(message, "accepted")) {
6558             return;
6559         }
6560         if (StrStr(message, "protover")) {
6561           /* Program is responding to input, so it's apparently done
6562              initializing, and this error message indicates it is
6563              protocol version 1.  So we don't need to wait any longer
6564              for it to initialize and send feature commands. */
6565           FeatureDone(cps, 1);
6566           cps->protocolVersion = 1;
6567           return;
6568         }
6569         cps->maybeThinking = FALSE;
6570
6571         if (StrStr(message, "draw")) {
6572             /* Program doesn't have "draw" command */
6573             cps->sendDrawOffers = 0;
6574             return;
6575         }
6576         if (cps->sendTime != 1 &&
6577             (StrStr(message, "time") || StrStr(message, "otim"))) {
6578           /* Program apparently doesn't have "time" or "otim" command */
6579           cps->sendTime = 0;
6580           return;
6581         }
6582         if (StrStr(message, "analyze")) {
6583             cps->analysisSupport = FALSE;
6584             cps->analyzing = FALSE;
6585             Reset(FALSE, TRUE);
6586             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6587             DisplayError(buf2, 0);
6588             return;
6589         }
6590         if (StrStr(message, "(no matching move)st")) {
6591           /* Special kludge for GNU Chess 4 only */
6592           cps->stKludge = TRUE;
6593           SendTimeControl(cps, movesPerSession, timeControl,
6594                           timeIncrement, appData.searchDepth,
6595                           searchTime);
6596           return;
6597         }
6598         if (StrStr(message, "(no matching move)sd")) {
6599           /* Special kludge for GNU Chess 4 only */
6600           cps->sdKludge = TRUE;
6601           SendTimeControl(cps, movesPerSession, timeControl,
6602                           timeIncrement, appData.searchDepth,
6603                           searchTime);
6604           return;
6605         }
6606         if (!StrStr(message, "llegal")) {
6607             return;
6608         }
6609         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6610             gameMode == IcsIdle) return;
6611         if (forwardMostMove <= backwardMostMove) return;
6612         if (pausing) PauseEvent();
6613       if(appData.forceIllegal) {
6614             // [HGM] illegal: machine refused move; force position after move into it
6615           SendToProgram("force\n", cps);
6616           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6617                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6618                 // when black is to move, while there might be nothing on a2 or black
6619                 // might already have the move. So send the board as if white has the move.
6620                 // But first we must change the stm of the engine, as it refused the last move
6621                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6622                 if(WhiteOnMove(forwardMostMove)) {
6623                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6624                     SendBoard(cps, forwardMostMove); // kludgeless board
6625                 } else {
6626                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6627                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6628                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6629                 }
6630           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6631             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6632                  gameMode == TwoMachinesPlay)
6633               SendToProgram("go\n", cps);
6634             return;
6635       } else
6636         if (gameMode == PlayFromGameFile) {
6637             /* Stop reading this game file */
6638             gameMode = EditGame;
6639             ModeHighlight();
6640         }
6641         currentMove = --forwardMostMove;
6642         DisplayMove(currentMove-1); /* before DisplayMoveError */
6643         SwitchClocks();
6644         DisplayBothClocks();
6645         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6646                 parseList[currentMove], cps->which);
6647         DisplayMoveError(buf1);
6648         DrawPosition(FALSE, boards[currentMove]);
6649
6650         /* [HGM] illegal-move claim should forfeit game when Xboard */
6651         /* only passes fully legal moves                            */
6652         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6653             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6654                                 "False illegal-move claim", GE_XBOARD );
6655         }
6656         return;
6657     }
6658     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6659         /* Program has a broken "time" command that
6660            outputs a string not ending in newline.
6661            Don't use it. */
6662         cps->sendTime = 0;
6663     }
6664     
6665     /*
6666      * If chess program startup fails, exit with an error message.
6667      * Attempts to recover here are futile.
6668      */
6669     if ((StrStr(message, "unknown host") != NULL)
6670         || (StrStr(message, "No remote directory") != NULL)
6671         || (StrStr(message, "not found") != NULL)
6672         || (StrStr(message, "No such file") != NULL)
6673         || (StrStr(message, "can't alloc") != NULL)
6674         || (StrStr(message, "Permission denied") != NULL)) {
6675
6676         cps->maybeThinking = FALSE;
6677         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6678                 cps->which, cps->program, cps->host, message);
6679         RemoveInputSource(cps->isr);
6680         DisplayFatalError(buf1, 0, 1);
6681         return;
6682     }
6683     
6684     /* 
6685      * Look for hint output
6686      */
6687     if (sscanf(message, "Hint: %s", buf1) == 1) {
6688         if (cps == &first && hintRequested) {
6689             hintRequested = FALSE;
6690             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6691                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6692                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6693                                     PosFlags(forwardMostMove),
6694                                     fromY, fromX, toY, toX, promoChar, buf1);
6695                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6696                 DisplayInformation(buf2);
6697             } else {
6698                 /* Hint move could not be parsed!? */
6699               snprintf(buf2, sizeof(buf2),
6700                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6701                         buf1, cps->which);
6702                 DisplayError(buf2, 0);
6703             }
6704         } else {
6705             strcpy(lastHint, buf1);
6706         }
6707         return;
6708     }
6709
6710     /*
6711      * Ignore other messages if game is not in progress
6712      */
6713     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6714         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6715
6716     /*
6717      * look for win, lose, draw, or draw offer
6718      */
6719     if (strncmp(message, "1-0", 3) == 0) {
6720         char *p, *q, *r = "";
6721         p = strchr(message, '{');
6722         if (p) {
6723             q = strchr(p, '}');
6724             if (q) {
6725                 *q = NULLCHAR;
6726                 r = p + 1;
6727             }
6728         }
6729         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6730         return;
6731     } else if (strncmp(message, "0-1", 3) == 0) {
6732         char *p, *q, *r = "";
6733         p = strchr(message, '{');
6734         if (p) {
6735             q = strchr(p, '}');
6736             if (q) {
6737                 *q = NULLCHAR;
6738                 r = p + 1;
6739             }
6740         }
6741         /* Kludge for Arasan 4.1 bug */
6742         if (strcmp(r, "Black resigns") == 0) {
6743             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6744             return;
6745         }
6746         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6747         return;
6748     } else if (strncmp(message, "1/2", 3) == 0) {
6749         char *p, *q, *r = "";
6750         p = strchr(message, '{');
6751         if (p) {
6752             q = strchr(p, '}');
6753             if (q) {
6754                 *q = NULLCHAR;
6755                 r = p + 1;
6756             }
6757         }
6758             
6759         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6760         return;
6761
6762     } else if (strncmp(message, "White resign", 12) == 0) {
6763         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6764         return;
6765     } else if (strncmp(message, "Black resign", 12) == 0) {
6766         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6767         return;
6768     } else if (strncmp(message, "White matches", 13) == 0 ||
6769                strncmp(message, "Black matches", 13) == 0   ) {
6770         /* [HGM] ignore GNUShogi noises */
6771         return;
6772     } else if (strncmp(message, "White", 5) == 0 &&
6773                message[5] != '(' &&
6774                StrStr(message, "Black") == NULL) {
6775         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6776         return;
6777     } else if (strncmp(message, "Black", 5) == 0 &&
6778                message[5] != '(') {
6779         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6780         return;
6781     } else if (strcmp(message, "resign") == 0 ||
6782                strcmp(message, "computer resigns") == 0) {
6783         switch (gameMode) {
6784           case MachinePlaysBlack:
6785           case IcsPlayingBlack:
6786             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6787             break;
6788           case MachinePlaysWhite:
6789           case IcsPlayingWhite:
6790             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6791             break;
6792           case TwoMachinesPlay:
6793             if (cps->twoMachinesColor[0] == 'w')
6794               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6795             else
6796               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6797             break;
6798           default:
6799             /* can't happen */
6800             break;
6801         }
6802         return;
6803     } else if (strncmp(message, "opponent mates", 14) == 0) {
6804         switch (gameMode) {
6805           case MachinePlaysBlack:
6806           case IcsPlayingBlack:
6807             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6808             break;
6809           case MachinePlaysWhite:
6810           case IcsPlayingWhite:
6811             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6812             break;
6813           case TwoMachinesPlay:
6814             if (cps->twoMachinesColor[0] == 'w')
6815               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6816             else
6817               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6818             break;
6819           default:
6820             /* can't happen */
6821             break;
6822         }
6823         return;
6824     } else if (strncmp(message, "computer mates", 14) == 0) {
6825         switch (gameMode) {
6826           case MachinePlaysBlack:
6827           case IcsPlayingBlack:
6828             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6829             break;
6830           case MachinePlaysWhite:
6831           case IcsPlayingWhite:
6832             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6833             break;
6834           case TwoMachinesPlay:
6835             if (cps->twoMachinesColor[0] == 'w')
6836               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6837             else
6838               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6839             break;
6840           default:
6841             /* can't happen */
6842             break;
6843         }
6844         return;
6845     } else if (strncmp(message, "checkmate", 9) == 0) {
6846         if (WhiteOnMove(forwardMostMove)) {
6847             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6848         } else {
6849             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6850         }
6851         return;
6852     } else if (strstr(message, "Draw") != NULL ||
6853                strstr(message, "game is a draw") != NULL) {
6854         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6855         return;
6856     } else if (strstr(message, "offer") != NULL &&
6857                strstr(message, "draw") != NULL) {
6858 #if ZIPPY
6859         if (appData.zippyPlay && first.initDone) {
6860             /* Relay offer to ICS */
6861             SendToICS(ics_prefix);
6862             SendToICS("draw\n");
6863         }
6864 #endif
6865         cps->offeredDraw = 2; /* valid until this engine moves twice */
6866         if (gameMode == TwoMachinesPlay) {
6867             if (cps->other->offeredDraw) {
6868                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6869             /* [HGM] in two-machine mode we delay relaying draw offer      */
6870             /* until after we also have move, to see if it is really claim */
6871             }
6872         } else if (gameMode == MachinePlaysWhite ||
6873                    gameMode == MachinePlaysBlack) {
6874           if (userOfferedDraw) {
6875             DisplayInformation(_("Machine accepts your draw offer"));
6876             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6877           } else {
6878             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6879           }
6880         }
6881     }
6882
6883     
6884     /*
6885      * Look for thinking output
6886      */
6887     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6888           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6889                                 ) {
6890         int plylev, mvleft, mvtot, curscore, time;
6891         char mvname[MOVE_LEN];
6892         u64 nodes; // [DM]
6893         char plyext;
6894         int ignore = FALSE;
6895         int prefixHint = FALSE;
6896         mvname[0] = NULLCHAR;
6897
6898         switch (gameMode) {
6899           case MachinePlaysBlack:
6900           case IcsPlayingBlack:
6901             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6902             break;
6903           case MachinePlaysWhite:
6904           case IcsPlayingWhite:
6905             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6906             break;
6907           case AnalyzeMode:
6908           case AnalyzeFile:
6909             break;
6910           case IcsObserving: /* [DM] icsEngineAnalyze */
6911             if (!appData.icsEngineAnalyze) ignore = TRUE;
6912             break;
6913           case TwoMachinesPlay:
6914             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6915                 ignore = TRUE;
6916             }
6917             break;
6918           default:
6919             ignore = TRUE;
6920             break;
6921         }
6922
6923         if (!ignore) {
6924             buf1[0] = NULLCHAR;
6925             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6926                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6927
6928                 if (plyext != ' ' && plyext != '\t') {
6929                     time *= 100;
6930                 }
6931
6932                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6933                 if( cps->scoreIsAbsolute && 
6934                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6935                 {
6936                     curscore = -curscore;
6937                 }
6938
6939
6940                 programStats.depth = plylev;
6941                 programStats.nodes = nodes;
6942                 programStats.time = time;
6943                 programStats.score = curscore;
6944                 programStats.got_only_move = 0;
6945
6946                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6947                         int ticklen;
6948
6949                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6950                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6951                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6952                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6953                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6954                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6955                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6956                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6957                 }
6958
6959                 /* Buffer overflow protection */
6960                 if (buf1[0] != NULLCHAR) {
6961                     if (strlen(buf1) >= sizeof(programStats.movelist)
6962                         && appData.debugMode) {
6963                         fprintf(debugFP,
6964                                 "PV is too long; using the first %u bytes.\n",
6965                                 (unsigned) sizeof(programStats.movelist) - 1);
6966                     }
6967
6968                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6969                 } else {
6970                     sprintf(programStats.movelist, " no PV\n");
6971                 }
6972
6973                 if (programStats.seen_stat) {
6974                     programStats.ok_to_send = 1;
6975                 }
6976
6977                 if (strchr(programStats.movelist, '(') != NULL) {
6978                     programStats.line_is_book = 1;
6979                     programStats.nr_moves = 0;
6980                     programStats.moves_left = 0;
6981                 } else {
6982                     programStats.line_is_book = 0;
6983                 }
6984
6985                 SendProgramStatsToFrontend( cps, &programStats );
6986
6987                 /* 
6988                     [AS] Protect the thinkOutput buffer from overflow... this
6989                     is only useful if buf1 hasn't overflowed first!
6990                 */
6991                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6992                         plylev, 
6993                         (gameMode == TwoMachinesPlay ?
6994                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6995                         ((double) curscore) / 100.0,
6996                         prefixHint ? lastHint : "",
6997                         prefixHint ? " " : "" );
6998
6999                 if( buf1[0] != NULLCHAR ) {
7000                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7001
7002                     if( strlen(buf1) > max_len ) {
7003                         if( appData.debugMode) {
7004                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7005                         }
7006                         buf1[max_len+1] = '\0';
7007                     }
7008
7009                     strcat( thinkOutput, buf1 );
7010                 }
7011
7012                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7013                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7014                     DisplayMove(currentMove - 1);
7015                 }
7016                 return;
7017
7018             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7019                 /* crafty (9.25+) says "(only move) <move>"
7020                  * if there is only 1 legal move
7021                  */
7022                 sscanf(p, "(only move) %s", buf1);
7023                 sprintf(thinkOutput, "%s (only move)", buf1);
7024                 sprintf(programStats.movelist, "%s (only move)", buf1);
7025                 programStats.depth = 1;
7026                 programStats.nr_moves = 1;
7027                 programStats.moves_left = 1;
7028                 programStats.nodes = 1;
7029                 programStats.time = 1;
7030                 programStats.got_only_move = 1;
7031
7032                 /* Not really, but we also use this member to
7033                    mean "line isn't going to change" (Crafty
7034                    isn't searching, so stats won't change) */
7035                 programStats.line_is_book = 1;
7036
7037                 SendProgramStatsToFrontend( cps, &programStats );
7038                 
7039                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7040                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7041                     DisplayMove(currentMove - 1);
7042                 }
7043                 return;
7044             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7045                               &time, &nodes, &plylev, &mvleft,
7046                               &mvtot, mvname) >= 5) {
7047                 /* The stat01: line is from Crafty (9.29+) in response
7048                    to the "." command */
7049                 programStats.seen_stat = 1;
7050                 cps->maybeThinking = TRUE;
7051
7052                 if (programStats.got_only_move || !appData.periodicUpdates)
7053                   return;
7054
7055                 programStats.depth = plylev;
7056                 programStats.time = time;
7057                 programStats.nodes = nodes;
7058                 programStats.moves_left = mvleft;
7059                 programStats.nr_moves = mvtot;
7060                 strcpy(programStats.move_name, mvname);
7061                 programStats.ok_to_send = 1;
7062                 programStats.movelist[0] = '\0';
7063
7064                 SendProgramStatsToFrontend( cps, &programStats );
7065
7066                 return;
7067
7068             } else if (strncmp(message,"++",2) == 0) {
7069                 /* Crafty 9.29+ outputs this */
7070                 programStats.got_fail = 2;
7071                 return;
7072
7073             } else if (strncmp(message,"--",2) == 0) {
7074                 /* Crafty 9.29+ outputs this */
7075                 programStats.got_fail = 1;
7076                 return;
7077
7078             } else if (thinkOutput[0] != NULLCHAR &&
7079                        strncmp(message, "    ", 4) == 0) {
7080                 unsigned message_len;
7081
7082                 p = message;
7083                 while (*p && *p == ' ') p++;
7084
7085                 message_len = strlen( p );
7086
7087                 /* [AS] Avoid buffer overflow */
7088                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7089                     strcat(thinkOutput, " ");
7090                     strcat(thinkOutput, p);
7091                 }
7092
7093                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7094                     strcat(programStats.movelist, " ");
7095                     strcat(programStats.movelist, p);
7096                 }
7097
7098                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7099                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7100                     DisplayMove(currentMove - 1);
7101                 }
7102                 return;
7103             }
7104         }
7105         else {
7106             buf1[0] = NULLCHAR;
7107
7108             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7109                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7110             {
7111                 ChessProgramStats cpstats;
7112
7113                 if (plyext != ' ' && plyext != '\t') {
7114                     time *= 100;
7115                 }
7116
7117                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7118                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7119                     curscore = -curscore;
7120                 }
7121
7122                 cpstats.depth = plylev;
7123                 cpstats.nodes = nodes;
7124                 cpstats.time = time;
7125                 cpstats.score = curscore;
7126                 cpstats.got_only_move = 0;
7127                 cpstats.movelist[0] = '\0';
7128
7129                 if (buf1[0] != NULLCHAR) {
7130                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7131                 }
7132
7133                 cpstats.ok_to_send = 0;
7134                 cpstats.line_is_book = 0;
7135                 cpstats.nr_moves = 0;
7136                 cpstats.moves_left = 0;
7137
7138                 SendProgramStatsToFrontend( cps, &cpstats );
7139             }
7140         }
7141     }
7142 }
7143
7144
7145 /* Parse a game score from the character string "game", and
7146    record it as the history of the current game.  The game
7147    score is NOT assumed to start from the standard position. 
7148    The display is not updated in any way.
7149    */
7150 void
7151 ParseGameHistory(game)
7152      char *game;
7153 {
7154     ChessMove moveType;
7155     int fromX, fromY, toX, toY, boardIndex;
7156     char promoChar;
7157     char *p, *q;
7158     char buf[MSG_SIZ];
7159
7160     if (appData.debugMode)
7161       fprintf(debugFP, "Parsing game history: %s\n", game);
7162
7163     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7164     gameInfo.site = StrSave(appData.icsHost);
7165     gameInfo.date = PGNDate();
7166     gameInfo.round = StrSave("-");
7167
7168     /* Parse out names of players */
7169     while (*game == ' ') game++;
7170     p = buf;
7171     while (*game != ' ') *p++ = *game++;
7172     *p = NULLCHAR;
7173     gameInfo.white = StrSave(buf);
7174     while (*game == ' ') game++;
7175     p = buf;
7176     while (*game != ' ' && *game != '\n') *p++ = *game++;
7177     *p = NULLCHAR;
7178     gameInfo.black = StrSave(buf);
7179
7180     /* Parse moves */
7181     boardIndex = blackPlaysFirst ? 1 : 0;
7182     yynewstr(game);
7183     for (;;) {
7184         yyboardindex = boardIndex;
7185         moveType = (ChessMove) yylex();
7186         switch (moveType) {
7187           case IllegalMove:             /* maybe suicide chess, etc. */
7188   if (appData.debugMode) {
7189     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7190     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7191     setbuf(debugFP, NULL);
7192   }
7193           case WhitePromotionChancellor:
7194           case BlackPromotionChancellor:
7195           case WhitePromotionArchbishop:
7196           case BlackPromotionArchbishop:
7197           case WhitePromotionQueen:
7198           case BlackPromotionQueen:
7199           case WhitePromotionRook:
7200           case BlackPromotionRook:
7201           case WhitePromotionBishop:
7202           case BlackPromotionBishop:
7203           case WhitePromotionKnight:
7204           case BlackPromotionKnight:
7205           case WhitePromotionKing:
7206           case BlackPromotionKing:
7207           case NormalMove:
7208           case WhiteCapturesEnPassant:
7209           case BlackCapturesEnPassant:
7210           case WhiteKingSideCastle:
7211           case WhiteQueenSideCastle:
7212           case BlackKingSideCastle:
7213           case BlackQueenSideCastle:
7214           case WhiteKingSideCastleWild:
7215           case WhiteQueenSideCastleWild:
7216           case BlackKingSideCastleWild:
7217           case BlackQueenSideCastleWild:
7218           /* PUSH Fabien */
7219           case WhiteHSideCastleFR:
7220           case WhiteASideCastleFR:
7221           case BlackHSideCastleFR:
7222           case BlackASideCastleFR:
7223           /* POP Fabien */
7224             fromX = currentMoveString[0] - AAA;
7225             fromY = currentMoveString[1] - ONE;
7226             toX = currentMoveString[2] - AAA;
7227             toY = currentMoveString[3] - ONE;
7228             promoChar = currentMoveString[4];
7229             break;
7230           case WhiteDrop:
7231           case BlackDrop:
7232             fromX = moveType == WhiteDrop ?
7233               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7234             (int) CharToPiece(ToLower(currentMoveString[0]));
7235             fromY = DROP_RANK;
7236             toX = currentMoveString[2] - AAA;
7237             toY = currentMoveString[3] - ONE;
7238             promoChar = NULLCHAR;
7239             break;
7240           case AmbiguousMove:
7241             /* bug? */
7242             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7243   if (appData.debugMode) {
7244     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7245     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7246     setbuf(debugFP, NULL);
7247   }
7248             DisplayError(buf, 0);
7249             return;
7250           case ImpossibleMove:
7251             /* bug? */
7252             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7253   if (appData.debugMode) {
7254     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7255     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7256     setbuf(debugFP, NULL);
7257   }
7258             DisplayError(buf, 0);
7259             return;
7260           case (ChessMove) 0:   /* end of file */
7261             if (boardIndex < backwardMostMove) {
7262                 /* Oops, gap.  How did that happen? */
7263                 DisplayError(_("Gap in move list"), 0);
7264                 return;
7265             }
7266             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7267             if (boardIndex > forwardMostMove) {
7268                 forwardMostMove = boardIndex;
7269             }
7270             return;
7271           case ElapsedTime:
7272             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7273                 strcat(parseList[boardIndex-1], " ");
7274                 strcat(parseList[boardIndex-1], yy_text);
7275             }
7276             continue;
7277           case Comment:
7278           case PGNTag:
7279           case NAG:
7280           default:
7281             /* ignore */
7282             continue;
7283           case WhiteWins:
7284           case BlackWins:
7285           case GameIsDrawn:
7286           case GameUnfinished:
7287             if (gameMode == IcsExamining) {
7288                 if (boardIndex < backwardMostMove) {
7289                     /* Oops, gap.  How did that happen? */
7290                     return;
7291                 }
7292                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7293                 return;
7294             }
7295             gameInfo.result = moveType;
7296             p = strchr(yy_text, '{');
7297             if (p == NULL) p = strchr(yy_text, '(');
7298             if (p == NULL) {
7299                 p = yy_text;
7300                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7301             } else {
7302                 q = strchr(p, *p == '{' ? '}' : ')');
7303                 if (q != NULL) *q = NULLCHAR;
7304                 p++;
7305             }
7306             gameInfo.resultDetails = StrSave(p);
7307             continue;
7308         }
7309         if (boardIndex >= forwardMostMove &&
7310             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7311             backwardMostMove = blackPlaysFirst ? 1 : 0;
7312             return;
7313         }
7314         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7315                                  fromY, fromX, toY, toX, promoChar,
7316                                  parseList[boardIndex]);
7317         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7318         /* currentMoveString is set as a side-effect of yylex */
7319         strcpy(moveList[boardIndex], currentMoveString);
7320         strcat(moveList[boardIndex], "\n");
7321         boardIndex++;
7322         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7323         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7324           case MT_NONE:
7325           case MT_STALEMATE:
7326           default:
7327             break;
7328           case MT_CHECK:
7329             if(gameInfo.variant != VariantShogi)
7330                 strcat(parseList[boardIndex - 1], "+");
7331             break;
7332           case MT_CHECKMATE:
7333           case MT_STAINMATE:
7334             strcat(parseList[boardIndex - 1], "#");
7335             break;
7336         }
7337     }
7338 }
7339
7340
7341 /* Apply a move to the given board  */
7342 void
7343 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7344      int fromX, fromY, toX, toY;
7345      int promoChar;
7346      Board board;
7347 {
7348   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7349
7350     /* [HGM] compute & store e.p. status and castling rights for new position */
7351     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7352     { int i;
7353
7354       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7355       oldEP = (signed char)board[EP_STATUS];
7356       board[EP_STATUS] = EP_NONE;
7357
7358       if( board[toY][toX] != EmptySquare ) 
7359            board[EP_STATUS] = EP_CAPTURE;  
7360
7361       if( board[fromY][fromX] == WhitePawn ) {
7362            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7363                board[EP_STATUS] = EP_PAWN_MOVE;
7364            if( toY-fromY==2) {
7365                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7366                         gameInfo.variant != VariantBerolina || toX < fromX)
7367                       board[EP_STATUS] = toX | berolina;
7368                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7369                         gameInfo.variant != VariantBerolina || toX > fromX) 
7370                       board[EP_STATUS] = toX;
7371            }
7372       } else 
7373       if( board[fromY][fromX] == BlackPawn ) {
7374            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7375                board[EP_STATUS] = EP_PAWN_MOVE; 
7376            if( toY-fromY== -2) {
7377                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7378                         gameInfo.variant != VariantBerolina || toX < fromX)
7379                       board[EP_STATUS] = toX | berolina;
7380                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7381                         gameInfo.variant != VariantBerolina || toX > fromX) 
7382                       board[EP_STATUS] = toX;
7383            }
7384        }
7385
7386        for(i=0; i<nrCastlingRights; i++) {
7387            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7388               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7389              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7390        }
7391
7392     }
7393
7394   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7395   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7396        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7397          
7398   if (fromX == toX && fromY == toY) return;
7399
7400   if (fromY == DROP_RANK) {
7401         /* must be first */
7402         piece = board[toY][toX] = (ChessSquare) fromX;
7403   } else {
7404      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7405      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7406      if(gameInfo.variant == VariantKnightmate)
7407          king += (int) WhiteUnicorn - (int) WhiteKing;
7408
7409     /* Code added by Tord: */
7410     /* FRC castling assumed when king captures friendly rook. */
7411     if (board[fromY][fromX] == WhiteKing &&
7412              board[toY][toX] == WhiteRook) {
7413       board[fromY][fromX] = EmptySquare;
7414       board[toY][toX] = EmptySquare;
7415       if(toX > fromX) {
7416         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7417       } else {
7418         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7419       }
7420     } else if (board[fromY][fromX] == BlackKing &&
7421                board[toY][toX] == BlackRook) {
7422       board[fromY][fromX] = EmptySquare;
7423       board[toY][toX] = EmptySquare;
7424       if(toX > fromX) {
7425         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7426       } else {
7427         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7428       }
7429     /* End of code added by Tord */
7430
7431     } else if (board[fromY][fromX] == king
7432         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7433         && toY == fromY && toX > fromX+1) {
7434         board[fromY][fromX] = EmptySquare;
7435         board[toY][toX] = king;
7436         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7437         board[fromY][BOARD_RGHT-1] = EmptySquare;
7438     } else if (board[fromY][fromX] == king
7439         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7440                && toY == fromY && toX < fromX-1) {
7441         board[fromY][fromX] = EmptySquare;
7442         board[toY][toX] = king;
7443         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7444         board[fromY][BOARD_LEFT] = EmptySquare;
7445     } else if (board[fromY][fromX] == WhitePawn
7446                && toY == BOARD_HEIGHT-1
7447                && gameInfo.variant != VariantXiangqi
7448                ) {
7449         /* white pawn promotion */
7450         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7451         if (board[toY][toX] == EmptySquare) {
7452             board[toY][toX] = WhiteQueen;
7453         }
7454         if(gameInfo.variant==VariantBughouse ||
7455            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7456             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7457         board[fromY][fromX] = EmptySquare;
7458     } else if ((fromY == BOARD_HEIGHT-4)
7459                && (toX != fromX)
7460                && gameInfo.variant != VariantXiangqi
7461                && gameInfo.variant != VariantBerolina
7462                && (board[fromY][fromX] == WhitePawn)
7463                && (board[toY][toX] == EmptySquare)) {
7464         board[fromY][fromX] = EmptySquare;
7465         board[toY][toX] = WhitePawn;
7466         captured = board[toY - 1][toX];
7467         board[toY - 1][toX] = EmptySquare;
7468     } else if ((fromY == BOARD_HEIGHT-4)
7469                && (toX == fromX)
7470                && gameInfo.variant == VariantBerolina
7471                && (board[fromY][fromX] == WhitePawn)
7472                && (board[toY][toX] == EmptySquare)) {
7473         board[fromY][fromX] = EmptySquare;
7474         board[toY][toX] = WhitePawn;
7475         if(oldEP & EP_BEROLIN_A) {
7476                 captured = board[fromY][fromX-1];
7477                 board[fromY][fromX-1] = EmptySquare;
7478         }else{  captured = board[fromY][fromX+1];
7479                 board[fromY][fromX+1] = EmptySquare;
7480         }
7481     } else if (board[fromY][fromX] == king
7482         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7483                && toY == fromY && toX > fromX+1) {
7484         board[fromY][fromX] = EmptySquare;
7485         board[toY][toX] = king;
7486         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7487         board[fromY][BOARD_RGHT-1] = EmptySquare;
7488     } else if (board[fromY][fromX] == king
7489         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7490                && toY == fromY && toX < fromX-1) {
7491         board[fromY][fromX] = EmptySquare;
7492         board[toY][toX] = king;
7493         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7494         board[fromY][BOARD_LEFT] = EmptySquare;
7495     } else if (fromY == 7 && fromX == 3
7496                && board[fromY][fromX] == BlackKing
7497                && toY == 7 && toX == 5) {
7498         board[fromY][fromX] = EmptySquare;
7499         board[toY][toX] = BlackKing;
7500         board[fromY][7] = EmptySquare;
7501         board[toY][4] = BlackRook;
7502     } else if (fromY == 7 && fromX == 3
7503                && board[fromY][fromX] == BlackKing
7504                && toY == 7 && toX == 1) {
7505         board[fromY][fromX] = EmptySquare;
7506         board[toY][toX] = BlackKing;
7507         board[fromY][0] = EmptySquare;
7508         board[toY][2] = BlackRook;
7509     } else if (board[fromY][fromX] == BlackPawn
7510                && toY == 0
7511                && gameInfo.variant != VariantXiangqi
7512                ) {
7513         /* black pawn promotion */
7514         board[0][toX] = CharToPiece(ToLower(promoChar));
7515         if (board[0][toX] == EmptySquare) {
7516             board[0][toX] = BlackQueen;
7517         }
7518         if(gameInfo.variant==VariantBughouse ||
7519            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7520             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7521         board[fromY][fromX] = EmptySquare;
7522     } else if ((fromY == 3)
7523                && (toX != fromX)
7524                && gameInfo.variant != VariantXiangqi
7525                && gameInfo.variant != VariantBerolina
7526                && (board[fromY][fromX] == BlackPawn)
7527                && (board[toY][toX] == EmptySquare)) {
7528         board[fromY][fromX] = EmptySquare;
7529         board[toY][toX] = BlackPawn;
7530         captured = board[toY + 1][toX];
7531         board[toY + 1][toX] = EmptySquare;
7532     } else if ((fromY == 3)
7533                && (toX == fromX)
7534                && gameInfo.variant == VariantBerolina
7535                && (board[fromY][fromX] == BlackPawn)
7536                && (board[toY][toX] == EmptySquare)) {
7537         board[fromY][fromX] = EmptySquare;
7538         board[toY][toX] = BlackPawn;
7539         if(oldEP & EP_BEROLIN_A) {
7540                 captured = board[fromY][fromX-1];
7541                 board[fromY][fromX-1] = EmptySquare;
7542         }else{  captured = board[fromY][fromX+1];
7543                 board[fromY][fromX+1] = EmptySquare;
7544         }
7545     } else {
7546         board[toY][toX] = board[fromY][fromX];
7547         board[fromY][fromX] = EmptySquare;
7548     }
7549
7550     /* [HGM] now we promote for Shogi, if needed */
7551     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7552         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7553   }
7554
7555     if (gameInfo.holdingsWidth != 0) {
7556
7557       /* !!A lot more code needs to be written to support holdings  */
7558       /* [HGM] OK, so I have written it. Holdings are stored in the */
7559       /* penultimate board files, so they are automaticlly stored   */
7560       /* in the game history.                                       */
7561       if (fromY == DROP_RANK) {
7562         /* Delete from holdings, by decreasing count */
7563         /* and erasing image if necessary            */
7564         p = (int) fromX;
7565         if(p < (int) BlackPawn) { /* white drop */
7566              p -= (int)WhitePawn;
7567                  p = PieceToNumber((ChessSquare)p);
7568              if(p >= gameInfo.holdingsSize) p = 0;
7569              if(--board[p][BOARD_WIDTH-2] <= 0)
7570                   board[p][BOARD_WIDTH-1] = EmptySquare;
7571              if((int)board[p][BOARD_WIDTH-2] < 0)
7572                         board[p][BOARD_WIDTH-2] = 0;
7573         } else {                  /* black drop */
7574              p -= (int)BlackPawn;
7575                  p = PieceToNumber((ChessSquare)p);
7576              if(p >= gameInfo.holdingsSize) p = 0;
7577              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7578                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7579              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7580                         board[BOARD_HEIGHT-1-p][1] = 0;
7581         }
7582       }
7583       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7584           && gameInfo.variant != VariantBughouse        ) {
7585         /* [HGM] holdings: Add to holdings, if holdings exist */
7586         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7587                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7588                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7589         }
7590         p = (int) captured;
7591         if (p >= (int) BlackPawn) {
7592           p -= (int)BlackPawn;
7593           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7594                   /* in Shogi restore piece to its original  first */
7595                   captured = (ChessSquare) (DEMOTED captured);
7596                   p = DEMOTED p;
7597           }
7598           p = PieceToNumber((ChessSquare)p);
7599           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7600           board[p][BOARD_WIDTH-2]++;
7601           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7602         } else {
7603           p -= (int)WhitePawn;
7604           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7605                   captured = (ChessSquare) (DEMOTED captured);
7606                   p = DEMOTED p;
7607           }
7608           p = PieceToNumber((ChessSquare)p);
7609           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7610           board[BOARD_HEIGHT-1-p][1]++;
7611           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7612         }
7613       }
7614     } else if (gameInfo.variant == VariantAtomic) {
7615       if (captured != EmptySquare) {
7616         int y, x;
7617         for (y = toY-1; y <= toY+1; y++) {
7618           for (x = toX-1; x <= toX+1; x++) {
7619             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7620                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7621               board[y][x] = EmptySquare;
7622             }
7623           }
7624         }
7625         board[toY][toX] = EmptySquare;
7626       }
7627     }
7628     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7629         /* [HGM] Shogi promotions */
7630         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7631     }
7632
7633     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7634                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7635         // [HGM] superchess: take promotion piece out of holdings
7636         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7637         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7638             if(!--board[k][BOARD_WIDTH-2])
7639                 board[k][BOARD_WIDTH-1] = EmptySquare;
7640         } else {
7641             if(!--board[BOARD_HEIGHT-1-k][1])
7642                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7643         }
7644     }
7645
7646 }
7647
7648 /* Updates forwardMostMove */
7649 void
7650 MakeMove(fromX, fromY, toX, toY, promoChar)
7651      int fromX, fromY, toX, toY;
7652      int promoChar;
7653 {
7654 //    forwardMostMove++; // [HGM] bare: moved downstream
7655
7656     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7657         int timeLeft; static int lastLoadFlag=0; int king, piece;
7658         piece = boards[forwardMostMove][fromY][fromX];
7659         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7660         if(gameInfo.variant == VariantKnightmate)
7661             king += (int) WhiteUnicorn - (int) WhiteKing;
7662         if(forwardMostMove == 0) {
7663             if(blackPlaysFirst) 
7664                 fprintf(serverMoves, "%s;", second.tidy);
7665             fprintf(serverMoves, "%s;", first.tidy);
7666             if(!blackPlaysFirst) 
7667                 fprintf(serverMoves, "%s;", second.tidy);
7668         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7669         lastLoadFlag = loadFlag;
7670         // print base move
7671         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7672         // print castling suffix
7673         if( toY == fromY && piece == king ) {
7674             if(toX-fromX > 1)
7675                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7676             if(fromX-toX >1)
7677                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7678         }
7679         // e.p. suffix
7680         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7681              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7682              boards[forwardMostMove][toY][toX] == EmptySquare
7683              && fromX != toX )
7684                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7685         // promotion suffix
7686         if(promoChar != NULLCHAR)
7687                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7688         if(!loadFlag) {
7689             fprintf(serverMoves, "/%d/%d",
7690                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7691             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7692             else                      timeLeft = blackTimeRemaining/1000;
7693             fprintf(serverMoves, "/%d", timeLeft);
7694         }
7695         fflush(serverMoves);
7696     }
7697
7698     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7699       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7700                         0, 1);
7701       return;
7702     }
7703     if (commentList[forwardMostMove+1] != NULL) {
7704         free(commentList[forwardMostMove+1]);
7705         commentList[forwardMostMove+1] = NULL;
7706     }
7707     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7708     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7709     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7710     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7711     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7712     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7713     gameInfo.result = GameUnfinished;
7714     if (gameInfo.resultDetails != NULL) {
7715         free(gameInfo.resultDetails);
7716         gameInfo.resultDetails = NULL;
7717     }
7718     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7719                               moveList[forwardMostMove - 1]);
7720     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7721                              PosFlags(forwardMostMove - 1),
7722                              fromY, fromX, toY, toX, promoChar,
7723                              parseList[forwardMostMove - 1]);
7724     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7725       case MT_NONE:
7726       case MT_STALEMATE:
7727       default:
7728         break;
7729       case MT_CHECK:
7730         if(gameInfo.variant != VariantShogi)
7731             strcat(parseList[forwardMostMove - 1], "+");
7732         break;
7733       case MT_CHECKMATE:
7734       case MT_STAINMATE:
7735         strcat(parseList[forwardMostMove - 1], "#");
7736         break;
7737     }
7738     if (appData.debugMode) {
7739         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7740     }
7741
7742 }
7743
7744 /* Updates currentMove if not pausing */
7745 void
7746 ShowMove(fromX, fromY, toX, toY)
7747 {
7748     int instant = (gameMode == PlayFromGameFile) ?
7749         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7750     if(appData.noGUI) return;
7751     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7752         if (!instant) {
7753             if (forwardMostMove == currentMove + 1) {
7754                 AnimateMove(boards[forwardMostMove - 1],
7755                             fromX, fromY, toX, toY);
7756             }
7757             if (appData.highlightLastMove) {
7758                 SetHighlights(fromX, fromY, toX, toY);
7759             }
7760         }
7761         currentMove = forwardMostMove;
7762     }
7763
7764     if (instant) return;
7765
7766     DisplayMove(currentMove - 1);
7767     DrawPosition(FALSE, boards[currentMove]);
7768     DisplayBothClocks();
7769     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7770 }
7771
7772 void SendEgtPath(ChessProgramState *cps)
7773 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7774         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7775
7776         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7777
7778         while(*p) {
7779             char c, *q = name+1, *r, *s;
7780
7781             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7782             while(*p && *p != ',') *q++ = *p++;
7783             *q++ = ':'; *q = 0;
7784             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7785                 strcmp(name, ",nalimov:") == 0 ) {
7786                 // take nalimov path from the menu-changeable option first, if it is defined
7787                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7788                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7789             } else
7790             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7791                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7792                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7793                 s = r = StrStr(s, ":") + 1; // beginning of path info
7794                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7795                 c = *r; *r = 0;             // temporarily null-terminate path info
7796                     *--q = 0;               // strip of trailig ':' from name
7797                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7798                 *r = c;
7799                 SendToProgram(buf,cps);     // send egtbpath command for this format
7800             }
7801             if(*p == ',') p++; // read away comma to position for next format name
7802         }
7803 }
7804
7805 void
7806 InitChessProgram(cps, setup)
7807      ChessProgramState *cps;
7808      int setup; /* [HGM] needed to setup FRC opening position */
7809 {
7810     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7811     if (appData.noChessProgram) return;
7812     hintRequested = FALSE;
7813     bookRequested = FALSE;
7814
7815     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7816     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7817     if(cps->memSize) { /* [HGM] memory */
7818         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7819         SendToProgram(buf, cps);
7820     }
7821     SendEgtPath(cps); /* [HGM] EGT */
7822     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7823         sprintf(buf, "cores %d\n", appData.smpCores);
7824         SendToProgram(buf, cps);
7825     }
7826
7827     SendToProgram(cps->initString, cps);
7828     if (gameInfo.variant != VariantNormal &&
7829         gameInfo.variant != VariantLoadable
7830         /* [HGM] also send variant if board size non-standard */
7831         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7832                                             ) {
7833       char *v = VariantName(gameInfo.variant);
7834       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7835         /* [HGM] in protocol 1 we have to assume all variants valid */
7836         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7837         DisplayFatalError(buf, 0, 1);
7838         return;
7839       }
7840
7841       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7842       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7843       if( gameInfo.variant == VariantXiangqi )
7844            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7845       if( gameInfo.variant == VariantShogi )
7846            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7847       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7848            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7849       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7850                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7851            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7852       if( gameInfo.variant == VariantCourier )
7853            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7854       if( gameInfo.variant == VariantSuper )
7855            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7856       if( gameInfo.variant == VariantGreat )
7857            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7858
7859       if(overruled) {
7860            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7861                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7862            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7863            if(StrStr(cps->variants, b) == NULL) { 
7864                // specific sized variant not known, check if general sizing allowed
7865                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7866                    if(StrStr(cps->variants, "boardsize") == NULL) {
7867                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7868                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7869                        DisplayFatalError(buf, 0, 1);
7870                        return;
7871                    }
7872                    /* [HGM] here we really should compare with the maximum supported board size */
7873                }
7874            }
7875       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7876       sprintf(buf, "variant %s\n", b);
7877       SendToProgram(buf, cps);
7878     }
7879     currentlyInitializedVariant = gameInfo.variant;
7880
7881     /* [HGM] send opening position in FRC to first engine */
7882     if(setup) {
7883           SendToProgram("force\n", cps);
7884           SendBoard(cps, 0);
7885           /* engine is now in force mode! Set flag to wake it up after first move. */
7886           setboardSpoiledMachineBlack = 1;
7887     }
7888
7889     if (cps->sendICS) {
7890       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7891       SendToProgram(buf, cps);
7892     }
7893     cps->maybeThinking = FALSE;
7894     cps->offeredDraw = 0;
7895     if (!appData.icsActive) {
7896         SendTimeControl(cps, movesPerSession, timeControl,
7897                         timeIncrement, appData.searchDepth,
7898                         searchTime);
7899     }
7900     if (appData.showThinking 
7901         // [HGM] thinking: four options require thinking output to be sent
7902         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7903                                 ) {
7904         SendToProgram("post\n", cps);
7905     }
7906     SendToProgram("hard\n", cps);
7907     if (!appData.ponderNextMove) {
7908         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7909            it without being sure what state we are in first.  "hard"
7910            is not a toggle, so that one is OK.
7911          */
7912         SendToProgram("easy\n", cps);
7913     }
7914     if (cps->usePing) {
7915       sprintf(buf, "ping %d\n", ++cps->lastPing);
7916       SendToProgram(buf, cps);
7917     }
7918     cps->initDone = TRUE;
7919 }   
7920
7921
7922 void
7923 StartChessProgram(cps)
7924      ChessProgramState *cps;
7925 {
7926     char buf[MSG_SIZ];
7927     int err;
7928
7929     if (appData.noChessProgram) return;
7930     cps->initDone = FALSE;
7931
7932     if (strcmp(cps->host, "localhost") == 0) {
7933         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7934     } else if (*appData.remoteShell == NULLCHAR) {
7935         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7936     } else {
7937         if (*appData.remoteUser == NULLCHAR) {
7938           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7939                     cps->program);
7940         } else {
7941           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7942                     cps->host, appData.remoteUser, cps->program);
7943         }
7944         err = StartChildProcess(buf, "", &cps->pr);
7945     }
7946     
7947     if (err != 0) {
7948         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7949         DisplayFatalError(buf, err, 1);
7950         cps->pr = NoProc;
7951         cps->isr = NULL;
7952         return;
7953     }
7954     
7955     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7956     if (cps->protocolVersion > 1) {
7957       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7958       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7959       cps->comboCnt = 0;  //                and values of combo boxes
7960       SendToProgram(buf, cps);
7961     } else {
7962       SendToProgram("xboard\n", cps);
7963     }
7964 }
7965
7966
7967 void
7968 TwoMachinesEventIfReady P((void))
7969 {
7970   if (first.lastPing != first.lastPong) {
7971     DisplayMessage("", _("Waiting for first chess program"));
7972     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7973     return;
7974   }
7975   if (second.lastPing != second.lastPong) {
7976     DisplayMessage("", _("Waiting for second chess program"));
7977     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7978     return;
7979   }
7980   ThawUI();
7981   TwoMachinesEvent();
7982 }
7983
7984 void
7985 NextMatchGame P((void))
7986 {
7987     int index; /* [HGM] autoinc: step load index during match */
7988     Reset(FALSE, TRUE);
7989     if (*appData.loadGameFile != NULLCHAR) {
7990         index = appData.loadGameIndex;
7991         if(index < 0) { // [HGM] autoinc
7992             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7993             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7994         } 
7995         LoadGameFromFile(appData.loadGameFile,
7996                          index,
7997                          appData.loadGameFile, FALSE);
7998     } else if (*appData.loadPositionFile != NULLCHAR) {
7999         index = appData.loadPositionIndex;
8000         if(index < 0) { // [HGM] autoinc
8001             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8002             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8003         } 
8004         LoadPositionFromFile(appData.loadPositionFile,
8005                              index,
8006                              appData.loadPositionFile);
8007     }
8008     TwoMachinesEventIfReady();
8009 }
8010
8011 void UserAdjudicationEvent( int result )
8012 {
8013     ChessMove gameResult = GameIsDrawn;
8014
8015     if( result > 0 ) {
8016         gameResult = WhiteWins;
8017     }
8018     else if( result < 0 ) {
8019         gameResult = BlackWins;
8020     }
8021
8022     if( gameMode == TwoMachinesPlay ) {
8023         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8024     }
8025 }
8026
8027
8028 // [HGM] save: calculate checksum of game to make games easily identifiable
8029 int StringCheckSum(char *s)
8030 {
8031         int i = 0;
8032         if(s==NULL) return 0;
8033         while(*s) i = i*259 + *s++;
8034         return i;
8035 }
8036
8037 int GameCheckSum()
8038 {
8039         int i, sum=0;
8040         for(i=backwardMostMove; i<forwardMostMove; i++) {
8041                 sum += pvInfoList[i].depth;
8042                 sum += StringCheckSum(parseList[i]);
8043                 sum += StringCheckSum(commentList[i]);
8044                 sum *= 261;
8045         }
8046         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8047         return sum + StringCheckSum(commentList[i]);
8048 } // end of save patch
8049
8050 void
8051 GameEnds(result, resultDetails, whosays)
8052      ChessMove result;
8053      char *resultDetails;
8054      int whosays;
8055 {
8056     GameMode nextGameMode;
8057     int isIcsGame;
8058     char buf[MSG_SIZ];
8059
8060     if(endingGame) return; /* [HGM] crash: forbid recursion */
8061     endingGame = 1;
8062
8063     if (appData.debugMode) {
8064       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8065               result, resultDetails ? resultDetails : "(null)", whosays);
8066     }
8067
8068     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8069         /* If we are playing on ICS, the server decides when the
8070            game is over, but the engine can offer to draw, claim 
8071            a draw, or resign. 
8072          */
8073 #if ZIPPY
8074         if (appData.zippyPlay && first.initDone) {
8075             if (result == GameIsDrawn) {
8076                 /* In case draw still needs to be claimed */
8077                 SendToICS(ics_prefix);
8078                 SendToICS("draw\n");
8079             } else if (StrCaseStr(resultDetails, "resign")) {
8080                 SendToICS(ics_prefix);
8081                 SendToICS("resign\n");
8082             }
8083         }
8084 #endif
8085         endingGame = 0; /* [HGM] crash */
8086         return;
8087     }
8088
8089     /* If we're loading the game from a file, stop */
8090     if (whosays == GE_FILE) {
8091       (void) StopLoadGameTimer();
8092       gameFileFP = NULL;
8093     }
8094
8095     /* Cancel draw offers */
8096     first.offeredDraw = second.offeredDraw = 0;
8097
8098     /* If this is an ICS game, only ICS can really say it's done;
8099        if not, anyone can. */
8100     isIcsGame = (gameMode == IcsPlayingWhite || 
8101                  gameMode == IcsPlayingBlack || 
8102                  gameMode == IcsObserving    || 
8103                  gameMode == IcsExamining);
8104
8105     if (!isIcsGame || whosays == GE_ICS) {
8106         /* OK -- not an ICS game, or ICS said it was done */
8107         StopClocks();
8108         if (!isIcsGame && !appData.noChessProgram) 
8109           SetUserThinkingEnables();
8110     
8111         /* [HGM] if a machine claims the game end we verify this claim */
8112         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8113             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8114                 char claimer;
8115                 ChessMove trueResult = (ChessMove) -1;
8116
8117                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8118                                             first.twoMachinesColor[0] :
8119                                             second.twoMachinesColor[0] ;
8120
8121                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8122                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8123                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8124                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8125                 } else
8126                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8127                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8128                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8129                 } else
8130                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8131                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8132                 }
8133
8134                 // now verify win claims, but not in drop games, as we don't understand those yet
8135                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8136                                                  || gameInfo.variant == VariantGreat) &&
8137                     (result == WhiteWins && claimer == 'w' ||
8138                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8139                       if (appData.debugMode) {
8140                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8141                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8142                       }
8143                       if(result != trueResult) {
8144                               sprintf(buf, "False win claim: '%s'", resultDetails);
8145                               result = claimer == 'w' ? BlackWins : WhiteWins;
8146                               resultDetails = buf;
8147                       }
8148                 } else
8149                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8150                     && (forwardMostMove <= backwardMostMove ||
8151                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8152                         (claimer=='b')==(forwardMostMove&1))
8153                                                                                   ) {
8154                       /* [HGM] verify: draws that were not flagged are false claims */
8155                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8156                       result = claimer == 'w' ? BlackWins : WhiteWins;
8157                       resultDetails = buf;
8158                 }
8159                 /* (Claiming a loss is accepted no questions asked!) */
8160             }
8161             /* [HGM] bare: don't allow bare King to win */
8162             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8163                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8164                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8165                && result != GameIsDrawn)
8166             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8167                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8168                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8169                         if(p >= 0 && p <= (int)WhiteKing) k++;
8170                 }
8171                 if (appData.debugMode) {
8172                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8173                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8174                 }
8175                 if(k <= 1) {
8176                         result = GameIsDrawn;
8177                         sprintf(buf, "%s but bare king", resultDetails);
8178                         resultDetails = buf;
8179                 }
8180             }
8181         }
8182
8183
8184         if(serverMoves != NULL && !loadFlag) { char c = '=';
8185             if(result==WhiteWins) c = '+';
8186             if(result==BlackWins) c = '-';
8187             if(resultDetails != NULL)
8188                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8189         }
8190         if (resultDetails != NULL) {
8191             gameInfo.result = result;
8192             gameInfo.resultDetails = StrSave(resultDetails);
8193
8194             /* display last move only if game was not loaded from file */
8195             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8196                 DisplayMove(currentMove - 1);
8197     
8198             if (forwardMostMove != 0) {
8199                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8200                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8201                                                                 ) {
8202                     if (*appData.saveGameFile != NULLCHAR) {
8203                         SaveGameToFile(appData.saveGameFile, TRUE);
8204                     } else if (appData.autoSaveGames) {
8205                         AutoSaveGame();
8206                     }
8207                     if (*appData.savePositionFile != NULLCHAR) {
8208                         SavePositionToFile(appData.savePositionFile);
8209                     }
8210                 }
8211             }
8212
8213             /* Tell program how game ended in case it is learning */
8214             /* [HGM] Moved this to after saving the PGN, just in case */
8215             /* engine died and we got here through time loss. In that */
8216             /* case we will get a fatal error writing the pipe, which */
8217             /* would otherwise lose us the PGN.                       */
8218             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8219             /* output during GameEnds should never be fatal anymore   */
8220             if (gameMode == MachinePlaysWhite ||
8221                 gameMode == MachinePlaysBlack ||
8222                 gameMode == TwoMachinesPlay ||
8223                 gameMode == IcsPlayingWhite ||
8224                 gameMode == IcsPlayingBlack ||
8225                 gameMode == BeginningOfGame) {
8226                 char buf[MSG_SIZ];
8227                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8228                         resultDetails);
8229                 if (first.pr != NoProc) {
8230                     SendToProgram(buf, &first);
8231                 }
8232                 if (second.pr != NoProc &&
8233                     gameMode == TwoMachinesPlay) {
8234                     SendToProgram(buf, &second);
8235                 }
8236             }
8237         }
8238
8239         if (appData.icsActive) {
8240             if (appData.quietPlay &&
8241                 (gameMode == IcsPlayingWhite ||
8242                  gameMode == IcsPlayingBlack)) {
8243                 SendToICS(ics_prefix);
8244                 SendToICS("set shout 1\n");
8245             }
8246             nextGameMode = IcsIdle;
8247             ics_user_moved = FALSE;
8248             /* clean up premove.  It's ugly when the game has ended and the
8249              * premove highlights are still on the board.
8250              */
8251             if (gotPremove) {
8252               gotPremove = FALSE;
8253               ClearPremoveHighlights();
8254               DrawPosition(FALSE, boards[currentMove]);
8255             }
8256             if (whosays == GE_ICS) {
8257                 switch (result) {
8258                 case WhiteWins:
8259                     if (gameMode == IcsPlayingWhite)
8260                         PlayIcsWinSound();
8261                     else if(gameMode == IcsPlayingBlack)
8262                         PlayIcsLossSound();
8263                     break;
8264                 case BlackWins:
8265                     if (gameMode == IcsPlayingBlack)
8266                         PlayIcsWinSound();
8267                     else if(gameMode == IcsPlayingWhite)
8268                         PlayIcsLossSound();
8269                     break;
8270                 case GameIsDrawn:
8271                     PlayIcsDrawSound();
8272                     break;
8273                 default:
8274                     PlayIcsUnfinishedSound();
8275                 }
8276             }
8277         } else if (gameMode == EditGame ||
8278                    gameMode == PlayFromGameFile || 
8279                    gameMode == AnalyzeMode || 
8280                    gameMode == AnalyzeFile) {
8281             nextGameMode = gameMode;
8282         } else {
8283             nextGameMode = EndOfGame;
8284         }
8285         pausing = FALSE;
8286         ModeHighlight();
8287     } else {
8288         nextGameMode = gameMode;
8289     }
8290
8291     if (appData.noChessProgram) {
8292         gameMode = nextGameMode;
8293         ModeHighlight();
8294         endingGame = 0; /* [HGM] crash */
8295         return;
8296     }
8297
8298     if (first.reuse) {
8299         /* Put first chess program into idle state */
8300         if (first.pr != NoProc &&
8301             (gameMode == MachinePlaysWhite ||
8302              gameMode == MachinePlaysBlack ||
8303              gameMode == TwoMachinesPlay ||
8304              gameMode == IcsPlayingWhite ||
8305              gameMode == IcsPlayingBlack ||
8306              gameMode == BeginningOfGame)) {
8307             SendToProgram("force\n", &first);
8308             if (first.usePing) {
8309               char buf[MSG_SIZ];
8310               sprintf(buf, "ping %d\n", ++first.lastPing);
8311               SendToProgram(buf, &first);
8312             }
8313         }
8314     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8315         /* Kill off first chess program */
8316         if (first.isr != NULL)
8317           RemoveInputSource(first.isr);
8318         first.isr = NULL;
8319     
8320         if (first.pr != NoProc) {
8321             ExitAnalyzeMode();
8322             DoSleep( appData.delayBeforeQuit );
8323             SendToProgram("quit\n", &first);
8324             DoSleep( appData.delayAfterQuit );
8325             DestroyChildProcess(first.pr, first.useSigterm);
8326         }
8327         first.pr = NoProc;
8328     }
8329     if (second.reuse) {
8330         /* Put second chess program into idle state */
8331         if (second.pr != NoProc &&
8332             gameMode == TwoMachinesPlay) {
8333             SendToProgram("force\n", &second);
8334             if (second.usePing) {
8335               char buf[MSG_SIZ];
8336               sprintf(buf, "ping %d\n", ++second.lastPing);
8337               SendToProgram(buf, &second);
8338             }
8339         }
8340     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8341         /* Kill off second chess program */
8342         if (second.isr != NULL)
8343           RemoveInputSource(second.isr);
8344         second.isr = NULL;
8345     
8346         if (second.pr != NoProc) {
8347             DoSleep( appData.delayBeforeQuit );
8348             SendToProgram("quit\n", &second);
8349             DoSleep( appData.delayAfterQuit );
8350             DestroyChildProcess(second.pr, second.useSigterm);
8351         }
8352         second.pr = NoProc;
8353     }
8354
8355     if (matchMode && gameMode == TwoMachinesPlay) {
8356         switch (result) {
8357         case WhiteWins:
8358           if (first.twoMachinesColor[0] == 'w') {
8359             first.matchWins++;
8360           } else {
8361             second.matchWins++;
8362           }
8363           break;
8364         case BlackWins:
8365           if (first.twoMachinesColor[0] == 'b') {
8366             first.matchWins++;
8367           } else {
8368             second.matchWins++;
8369           }
8370           break;
8371         default:
8372           break;
8373         }
8374         if (matchGame < appData.matchGames) {
8375             char *tmp;
8376             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8377                 tmp = first.twoMachinesColor;
8378                 first.twoMachinesColor = second.twoMachinesColor;
8379                 second.twoMachinesColor = tmp;
8380             }
8381             gameMode = nextGameMode;
8382             matchGame++;
8383             if(appData.matchPause>10000 || appData.matchPause<10)
8384                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8385             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8386             endingGame = 0; /* [HGM] crash */
8387             return;
8388         } else {
8389             char buf[MSG_SIZ];
8390             gameMode = nextGameMode;
8391             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8392                     first.tidy, second.tidy,
8393                     first.matchWins, second.matchWins,
8394                     appData.matchGames - (first.matchWins + second.matchWins));
8395             DisplayFatalError(buf, 0, 0);
8396         }
8397     }
8398     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8399         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8400       ExitAnalyzeMode();
8401     gameMode = nextGameMode;
8402     ModeHighlight();
8403     endingGame = 0;  /* [HGM] crash */
8404 }
8405
8406 /* Assumes program was just initialized (initString sent).
8407    Leaves program in force mode. */
8408 void
8409 FeedMovesToProgram(cps, upto) 
8410      ChessProgramState *cps;
8411      int upto;
8412 {
8413     int i;
8414     
8415     if (appData.debugMode)
8416       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8417               startedFromSetupPosition ? "position and " : "",
8418               backwardMostMove, upto, cps->which);
8419     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8420         // [HGM] variantswitch: make engine aware of new variant
8421         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8422                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8423         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8424         SendToProgram(buf, cps);
8425         currentlyInitializedVariant = gameInfo.variant;
8426     }
8427     SendToProgram("force\n", cps);
8428     if (startedFromSetupPosition) {
8429         SendBoard(cps, backwardMostMove);
8430     if (appData.debugMode) {
8431         fprintf(debugFP, "feedMoves\n");
8432     }
8433     }
8434     for (i = backwardMostMove; i < upto; i++) {
8435         SendMoveToProgram(i, cps);
8436     }
8437 }
8438
8439
8440 void
8441 ResurrectChessProgram()
8442 {
8443      /* The chess program may have exited.
8444         If so, restart it and feed it all the moves made so far. */
8445
8446     if (appData.noChessProgram || first.pr != NoProc) return;
8447     
8448     StartChessProgram(&first);
8449     InitChessProgram(&first, FALSE);
8450     FeedMovesToProgram(&first, currentMove);
8451
8452     if (!first.sendTime) {
8453         /* can't tell gnuchess what its clock should read,
8454            so we bow to its notion. */
8455         ResetClocks();
8456         timeRemaining[0][currentMove] = whiteTimeRemaining;
8457         timeRemaining[1][currentMove] = blackTimeRemaining;
8458     }
8459
8460     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8461                 appData.icsEngineAnalyze) && first.analysisSupport) {
8462       SendToProgram("analyze\n", &first);
8463       first.analyzing = TRUE;
8464     }
8465 }
8466
8467 /*
8468  * Button procedures
8469  */
8470 void
8471 Reset(redraw, init)
8472      int redraw, init;
8473 {
8474     int i;
8475
8476     if (appData.debugMode) {
8477         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8478                 redraw, init, gameMode);
8479     }
8480     CleanupTail(); // [HGM] vari: delete any stored variations
8481     pausing = pauseExamInvalid = FALSE;
8482     startedFromSetupPosition = blackPlaysFirst = FALSE;
8483     firstMove = TRUE;
8484     whiteFlag = blackFlag = FALSE;
8485     userOfferedDraw = FALSE;
8486     hintRequested = bookRequested = FALSE;
8487     first.maybeThinking = FALSE;
8488     second.maybeThinking = FALSE;
8489     first.bookSuspend = FALSE; // [HGM] book
8490     second.bookSuspend = FALSE;
8491     thinkOutput[0] = NULLCHAR;
8492     lastHint[0] = NULLCHAR;
8493     ClearGameInfo(&gameInfo);
8494     gameInfo.variant = StringToVariant(appData.variant);
8495     ics_user_moved = ics_clock_paused = FALSE;
8496     ics_getting_history = H_FALSE;
8497     ics_gamenum = -1;
8498     white_holding[0] = black_holding[0] = NULLCHAR;
8499     ClearProgramStats();
8500     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8501     
8502     ResetFrontEnd();
8503     ClearHighlights();
8504     flipView = appData.flipView;
8505     ClearPremoveHighlights();
8506     gotPremove = FALSE;
8507     alarmSounded = FALSE;
8508
8509     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8510     if(appData.serverMovesName != NULL) {
8511         /* [HGM] prepare to make moves file for broadcasting */
8512         clock_t t = clock();
8513         if(serverMoves != NULL) fclose(serverMoves);
8514         serverMoves = fopen(appData.serverMovesName, "r");
8515         if(serverMoves != NULL) {
8516             fclose(serverMoves);
8517             /* delay 15 sec before overwriting, so all clients can see end */
8518             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8519         }
8520         serverMoves = fopen(appData.serverMovesName, "w");
8521     }
8522
8523     ExitAnalyzeMode();
8524     gameMode = BeginningOfGame;
8525     ModeHighlight();
8526     if(appData.icsActive) gameInfo.variant = VariantNormal;
8527     currentMove = forwardMostMove = backwardMostMove = 0;
8528     InitPosition(redraw);
8529     for (i = 0; i < MAX_MOVES; i++) {
8530         if (commentList[i] != NULL) {
8531             free(commentList[i]);
8532             commentList[i] = NULL;
8533         }
8534     }
8535     ResetClocks();
8536     timeRemaining[0][0] = whiteTimeRemaining;
8537     timeRemaining[1][0] = blackTimeRemaining;
8538     if (first.pr == NULL) {
8539         StartChessProgram(&first);
8540     }
8541     if (init) {
8542             InitChessProgram(&first, startedFromSetupPosition);
8543     }
8544     DisplayTitle("");
8545     DisplayMessage("", "");
8546     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8547     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8548 }
8549
8550 void
8551 AutoPlayGameLoop()
8552 {
8553     for (;;) {
8554         if (!AutoPlayOneMove())
8555           return;
8556         if (matchMode || appData.timeDelay == 0)
8557           continue;
8558         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8559           return;
8560         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8561         break;
8562     }
8563 }
8564
8565
8566 int
8567 AutoPlayOneMove()
8568 {
8569     int fromX, fromY, toX, toY;
8570
8571     if (appData.debugMode) {
8572       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8573     }
8574
8575     if (gameMode != PlayFromGameFile)
8576       return FALSE;
8577
8578     if (currentMove >= forwardMostMove) {
8579       gameMode = EditGame;
8580       ModeHighlight();
8581
8582       /* [AS] Clear current move marker at the end of a game */
8583       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8584
8585       return FALSE;
8586     }
8587     
8588     toX = moveList[currentMove][2] - AAA;
8589     toY = moveList[currentMove][3] - ONE;
8590
8591     if (moveList[currentMove][1] == '@') {
8592         if (appData.highlightLastMove) {
8593             SetHighlights(-1, -1, toX, toY);
8594         }
8595     } else {
8596         fromX = moveList[currentMove][0] - AAA;
8597         fromY = moveList[currentMove][1] - ONE;
8598
8599         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8600
8601         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8602
8603         if (appData.highlightLastMove) {
8604             SetHighlights(fromX, fromY, toX, toY);
8605         }
8606     }
8607     DisplayMove(currentMove);
8608     SendMoveToProgram(currentMove++, &first);
8609     DisplayBothClocks();
8610     DrawPosition(FALSE, boards[currentMove]);
8611     // [HGM] PV info: always display, routine tests if empty
8612     DisplayComment(currentMove - 1, commentList[currentMove]);
8613     return TRUE;
8614 }
8615
8616
8617 int
8618 LoadGameOneMove(readAhead)
8619      ChessMove readAhead;
8620 {
8621     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8622     char promoChar = NULLCHAR;
8623     ChessMove moveType;
8624     char move[MSG_SIZ];
8625     char *p, *q;
8626     
8627     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8628         gameMode != AnalyzeMode && gameMode != Training) {
8629         gameFileFP = NULL;
8630         return FALSE;
8631     }
8632     
8633     yyboardindex = forwardMostMove;
8634     if (readAhead != (ChessMove)0) {
8635       moveType = readAhead;
8636     } else {
8637       if (gameFileFP == NULL)
8638           return FALSE;
8639       moveType = (ChessMove) yylex();
8640     }
8641     
8642     done = FALSE;
8643     switch (moveType) {
8644       case Comment:
8645         if (appData.debugMode) 
8646           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8647         p = yy_text;
8648
8649         /* append the comment but don't display it */
8650         AppendComment(currentMove, p, FALSE);
8651         return TRUE;
8652
8653       case WhiteCapturesEnPassant:
8654       case BlackCapturesEnPassant:
8655       case WhitePromotionChancellor:
8656       case BlackPromotionChancellor:
8657       case WhitePromotionArchbishop:
8658       case BlackPromotionArchbishop:
8659       case WhitePromotionCentaur:
8660       case BlackPromotionCentaur:
8661       case WhitePromotionQueen:
8662       case BlackPromotionQueen:
8663       case WhitePromotionRook:
8664       case BlackPromotionRook:
8665       case WhitePromotionBishop:
8666       case BlackPromotionBishop:
8667       case WhitePromotionKnight:
8668       case BlackPromotionKnight:
8669       case WhitePromotionKing:
8670       case BlackPromotionKing:
8671       case NormalMove:
8672       case WhiteKingSideCastle:
8673       case WhiteQueenSideCastle:
8674       case BlackKingSideCastle:
8675       case BlackQueenSideCastle:
8676       case WhiteKingSideCastleWild:
8677       case WhiteQueenSideCastleWild:
8678       case BlackKingSideCastleWild:
8679       case BlackQueenSideCastleWild:
8680       /* PUSH Fabien */
8681       case WhiteHSideCastleFR:
8682       case WhiteASideCastleFR:
8683       case BlackHSideCastleFR:
8684       case BlackASideCastleFR:
8685       /* POP Fabien */
8686         if (appData.debugMode)
8687           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8688         fromX = currentMoveString[0] - AAA;
8689         fromY = currentMoveString[1] - ONE;
8690         toX = currentMoveString[2] - AAA;
8691         toY = currentMoveString[3] - ONE;
8692         promoChar = currentMoveString[4];
8693         break;
8694
8695       case WhiteDrop:
8696       case BlackDrop:
8697         if (appData.debugMode)
8698           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8699         fromX = moveType == WhiteDrop ?
8700           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8701         (int) CharToPiece(ToLower(currentMoveString[0]));
8702         fromY = DROP_RANK;
8703         toX = currentMoveString[2] - AAA;
8704         toY = currentMoveString[3] - ONE;
8705         break;
8706
8707       case WhiteWins:
8708       case BlackWins:
8709       case GameIsDrawn:
8710       case GameUnfinished:
8711         if (appData.debugMode)
8712           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8713         p = strchr(yy_text, '{');
8714         if (p == NULL) p = strchr(yy_text, '(');
8715         if (p == NULL) {
8716             p = yy_text;
8717             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8718         } else {
8719             q = strchr(p, *p == '{' ? '}' : ')');
8720             if (q != NULL) *q = NULLCHAR;
8721             p++;
8722         }
8723         GameEnds(moveType, p, GE_FILE);
8724         done = TRUE;
8725         if (cmailMsgLoaded) {
8726             ClearHighlights();
8727             flipView = WhiteOnMove(currentMove);
8728             if (moveType == GameUnfinished) flipView = !flipView;
8729             if (appData.debugMode)
8730               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8731         }
8732         break;
8733
8734       case (ChessMove) 0:       /* end of file */
8735         if (appData.debugMode)
8736           fprintf(debugFP, "Parser hit end of file\n");
8737         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8738           case MT_NONE:
8739           case MT_CHECK:
8740             break;
8741           case MT_CHECKMATE:
8742           case MT_STAINMATE:
8743             if (WhiteOnMove(currentMove)) {
8744                 GameEnds(BlackWins, "Black mates", GE_FILE);
8745             } else {
8746                 GameEnds(WhiteWins, "White mates", GE_FILE);
8747             }
8748             break;
8749           case MT_STALEMATE:
8750             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8751             break;
8752         }
8753         done = TRUE;
8754         break;
8755
8756       case MoveNumberOne:
8757         if (lastLoadGameStart == GNUChessGame) {
8758             /* GNUChessGames have numbers, but they aren't move numbers */
8759             if (appData.debugMode)
8760               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8761                       yy_text, (int) moveType);
8762             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8763         }
8764         /* else fall thru */
8765
8766       case XBoardGame:
8767       case GNUChessGame:
8768       case PGNTag:
8769         /* Reached start of next game in file */
8770         if (appData.debugMode)
8771           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8772         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8773           case MT_NONE:
8774           case MT_CHECK:
8775             break;
8776           case MT_CHECKMATE:
8777           case MT_STAINMATE:
8778             if (WhiteOnMove(currentMove)) {
8779                 GameEnds(BlackWins, "Black mates", GE_FILE);
8780             } else {
8781                 GameEnds(WhiteWins, "White mates", GE_FILE);
8782             }
8783             break;
8784           case MT_STALEMATE:
8785             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8786             break;
8787         }
8788         done = TRUE;
8789         break;
8790
8791       case PositionDiagram:     /* should not happen; ignore */
8792       case ElapsedTime:         /* ignore */
8793       case NAG:                 /* ignore */
8794         if (appData.debugMode)
8795           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8796                   yy_text, (int) moveType);
8797         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8798
8799       case IllegalMove:
8800         if (appData.testLegality) {
8801             if (appData.debugMode)
8802               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8803             sprintf(move, _("Illegal move: %d.%s%s"),
8804                     (forwardMostMove / 2) + 1,
8805                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8806             DisplayError(move, 0);
8807             done = TRUE;
8808         } else {
8809             if (appData.debugMode)
8810               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8811                       yy_text, currentMoveString);
8812             fromX = currentMoveString[0] - AAA;
8813             fromY = currentMoveString[1] - ONE;
8814             toX = currentMoveString[2] - AAA;
8815             toY = currentMoveString[3] - ONE;
8816             promoChar = currentMoveString[4];
8817         }
8818         break;
8819
8820       case AmbiguousMove:
8821         if (appData.debugMode)
8822           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8823         sprintf(move, _("Ambiguous move: %d.%s%s"),
8824                 (forwardMostMove / 2) + 1,
8825                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8826         DisplayError(move, 0);
8827         done = TRUE;
8828         break;
8829
8830       default:
8831       case ImpossibleMove:
8832         if (appData.debugMode)
8833           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8834         sprintf(move, _("Illegal move: %d.%s%s"),
8835                 (forwardMostMove / 2) + 1,
8836                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8837         DisplayError(move, 0);
8838         done = TRUE;
8839         break;
8840     }
8841
8842     if (done) {
8843         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8844             DrawPosition(FALSE, boards[currentMove]);
8845             DisplayBothClocks();
8846             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8847               DisplayComment(currentMove - 1, commentList[currentMove]);
8848         }
8849         (void) StopLoadGameTimer();
8850         gameFileFP = NULL;
8851         cmailOldMove = forwardMostMove;
8852         return FALSE;
8853     } else {
8854         /* currentMoveString is set as a side-effect of yylex */
8855         strcat(currentMoveString, "\n");
8856         strcpy(moveList[forwardMostMove], currentMoveString);
8857         
8858         thinkOutput[0] = NULLCHAR;
8859         MakeMove(fromX, fromY, toX, toY, promoChar);
8860         currentMove = forwardMostMove;
8861         return TRUE;
8862     }
8863 }
8864
8865 /* Load the nth game from the given file */
8866 int
8867 LoadGameFromFile(filename, n, title, useList)
8868      char *filename;
8869      int n;
8870      char *title;
8871      /*Boolean*/ int useList;
8872 {
8873     FILE *f;
8874     char buf[MSG_SIZ];
8875
8876     if (strcmp(filename, "-") == 0) {
8877         f = stdin;
8878         title = "stdin";
8879     } else {
8880         f = fopen(filename, "rb");
8881         if (f == NULL) {
8882           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8883             DisplayError(buf, errno);
8884             return FALSE;
8885         }
8886     }
8887     if (fseek(f, 0, 0) == -1) {
8888         /* f is not seekable; probably a pipe */
8889         useList = FALSE;
8890     }
8891     if (useList && n == 0) {
8892         int error = GameListBuild(f);
8893         if (error) {
8894             DisplayError(_("Cannot build game list"), error);
8895         } else if (!ListEmpty(&gameList) &&
8896                    ((ListGame *) gameList.tailPred)->number > 1) {
8897             GameListPopUp(f, title);
8898             return TRUE;
8899         }
8900         GameListDestroy();
8901         n = 1;
8902     }
8903     if (n == 0) n = 1;
8904     return LoadGame(f, n, title, FALSE);
8905 }
8906
8907
8908 void
8909 MakeRegisteredMove()
8910 {
8911     int fromX, fromY, toX, toY;
8912     char promoChar;
8913     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8914         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8915           case CMAIL_MOVE:
8916           case CMAIL_DRAW:
8917             if (appData.debugMode)
8918               fprintf(debugFP, "Restoring %s for game %d\n",
8919                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8920     
8921             thinkOutput[0] = NULLCHAR;
8922             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8923             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8924             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8925             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8926             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8927             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8928             MakeMove(fromX, fromY, toX, toY, promoChar);
8929             ShowMove(fromX, fromY, toX, toY);
8930               
8931             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8932               case MT_NONE:
8933               case MT_CHECK:
8934                 break;
8935                 
8936               case MT_CHECKMATE:
8937               case MT_STAINMATE:
8938                 if (WhiteOnMove(currentMove)) {
8939                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8940                 } else {
8941                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8942                 }
8943                 break;
8944                 
8945               case MT_STALEMATE:
8946                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8947                 break;
8948             }
8949
8950             break;
8951             
8952           case CMAIL_RESIGN:
8953             if (WhiteOnMove(currentMove)) {
8954                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8955             } else {
8956                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8957             }
8958             break;
8959             
8960           case CMAIL_ACCEPT:
8961             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8962             break;
8963               
8964           default:
8965             break;
8966         }
8967     }
8968
8969     return;
8970 }
8971
8972 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8973 int
8974 CmailLoadGame(f, gameNumber, title, useList)
8975      FILE *f;
8976      int gameNumber;
8977      char *title;
8978      int useList;
8979 {
8980     int retVal;
8981
8982     if (gameNumber > nCmailGames) {
8983         DisplayError(_("No more games in this message"), 0);
8984         return FALSE;
8985     }
8986     if (f == lastLoadGameFP) {
8987         int offset = gameNumber - lastLoadGameNumber;
8988         if (offset == 0) {
8989             cmailMsg[0] = NULLCHAR;
8990             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8991                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8992                 nCmailMovesRegistered--;
8993             }
8994             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8995             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8996                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8997             }
8998         } else {
8999             if (! RegisterMove()) return FALSE;
9000         }
9001     }
9002
9003     retVal = LoadGame(f, gameNumber, title, useList);
9004
9005     /* Make move registered during previous look at this game, if any */
9006     MakeRegisteredMove();
9007
9008     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9009         commentList[currentMove]
9010           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9011         DisplayComment(currentMove - 1, commentList[currentMove]);
9012     }
9013
9014     return retVal;
9015 }
9016
9017 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9018 int
9019 ReloadGame(offset)
9020      int offset;
9021 {
9022     int gameNumber = lastLoadGameNumber + offset;
9023     if (lastLoadGameFP == NULL) {
9024         DisplayError(_("No game has been loaded yet"), 0);
9025         return FALSE;
9026     }
9027     if (gameNumber <= 0) {
9028         DisplayError(_("Can't back up any further"), 0);
9029         return FALSE;
9030     }
9031     if (cmailMsgLoaded) {
9032         return CmailLoadGame(lastLoadGameFP, gameNumber,
9033                              lastLoadGameTitle, lastLoadGameUseList);
9034     } else {
9035         return LoadGame(lastLoadGameFP, gameNumber,
9036                         lastLoadGameTitle, lastLoadGameUseList);
9037     }
9038 }
9039
9040
9041
9042 /* Load the nth game from open file f */
9043 int
9044 LoadGame(f, gameNumber, title, useList)
9045      FILE *f;
9046      int gameNumber;
9047      char *title;
9048      int useList;
9049 {
9050     ChessMove cm;
9051     char buf[MSG_SIZ];
9052     int gn = gameNumber;
9053     ListGame *lg = NULL;
9054     int numPGNTags = 0;
9055     int err;
9056     GameMode oldGameMode;
9057     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9058
9059     if (appData.debugMode) 
9060         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9061
9062     if (gameMode == Training )
9063         SetTrainingModeOff();
9064
9065     oldGameMode = gameMode;
9066     if (gameMode != BeginningOfGame) {
9067       Reset(FALSE, TRUE);
9068     }
9069
9070     gameFileFP = f;
9071     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9072         fclose(lastLoadGameFP);
9073     }
9074
9075     if (useList) {
9076         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9077         
9078         if (lg) {
9079             fseek(f, lg->offset, 0);
9080             GameListHighlight(gameNumber);
9081             gn = 1;
9082         }
9083         else {
9084             DisplayError(_("Game number out of range"), 0);
9085             return FALSE;
9086         }
9087     } else {
9088         GameListDestroy();
9089         if (fseek(f, 0, 0) == -1) {
9090             if (f == lastLoadGameFP ?
9091                 gameNumber == lastLoadGameNumber + 1 :
9092                 gameNumber == 1) {
9093                 gn = 1;
9094             } else {
9095                 DisplayError(_("Can't seek on game file"), 0);
9096                 return FALSE;
9097             }
9098         }
9099     }
9100     lastLoadGameFP = f;
9101     lastLoadGameNumber = gameNumber;
9102     strcpy(lastLoadGameTitle, title);
9103     lastLoadGameUseList = useList;
9104
9105     yynewfile(f);
9106
9107     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9108       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9109                 lg->gameInfo.black);
9110             DisplayTitle(buf);
9111     } else if (*title != NULLCHAR) {
9112         if (gameNumber > 1) {
9113             sprintf(buf, "%s %d", title, gameNumber);
9114             DisplayTitle(buf);
9115         } else {
9116             DisplayTitle(title);
9117         }
9118     }
9119
9120     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9121         gameMode = PlayFromGameFile;
9122         ModeHighlight();
9123     }
9124
9125     currentMove = forwardMostMove = backwardMostMove = 0;
9126     CopyBoard(boards[0], initialPosition);
9127     StopClocks();
9128
9129     /*
9130      * Skip the first gn-1 games in the file.
9131      * Also skip over anything that precedes an identifiable 
9132      * start of game marker, to avoid being confused by 
9133      * garbage at the start of the file.  Currently 
9134      * recognized start of game markers are the move number "1",
9135      * the pattern "gnuchess .* game", the pattern
9136      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9137      * A game that starts with one of the latter two patterns
9138      * will also have a move number 1, possibly
9139      * following a position diagram.
9140      * 5-4-02: Let's try being more lenient and allowing a game to
9141      * start with an unnumbered move.  Does that break anything?
9142      */
9143     cm = lastLoadGameStart = (ChessMove) 0;
9144     while (gn > 0) {
9145         yyboardindex = forwardMostMove;
9146         cm = (ChessMove) yylex();
9147         switch (cm) {
9148           case (ChessMove) 0:
9149             if (cmailMsgLoaded) {
9150                 nCmailGames = CMAIL_MAX_GAMES - gn;
9151             } else {
9152                 Reset(TRUE, TRUE);
9153                 DisplayError(_("Game not found in file"), 0);
9154             }
9155             return FALSE;
9156
9157           case GNUChessGame:
9158           case XBoardGame:
9159             gn--;
9160             lastLoadGameStart = cm;
9161             break;
9162             
9163           case MoveNumberOne:
9164             switch (lastLoadGameStart) {
9165               case GNUChessGame:
9166               case XBoardGame:
9167               case PGNTag:
9168                 break;
9169               case MoveNumberOne:
9170               case (ChessMove) 0:
9171                 gn--;           /* count this game */
9172                 lastLoadGameStart = cm;
9173                 break;
9174               default:
9175                 /* impossible */
9176                 break;
9177             }
9178             break;
9179
9180           case PGNTag:
9181             switch (lastLoadGameStart) {
9182               case GNUChessGame:
9183               case PGNTag:
9184               case MoveNumberOne:
9185               case (ChessMove) 0:
9186                 gn--;           /* count this game */
9187                 lastLoadGameStart = cm;
9188                 break;
9189               case XBoardGame:
9190                 lastLoadGameStart = cm; /* game counted already */
9191                 break;
9192               default:
9193                 /* impossible */
9194                 break;
9195             }
9196             if (gn > 0) {
9197                 do {
9198                     yyboardindex = forwardMostMove;
9199                     cm = (ChessMove) yylex();
9200                 } while (cm == PGNTag || cm == Comment);
9201             }
9202             break;
9203
9204           case WhiteWins:
9205           case BlackWins:
9206           case GameIsDrawn:
9207             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9208                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9209                     != CMAIL_OLD_RESULT) {
9210                     nCmailResults ++ ;
9211                     cmailResult[  CMAIL_MAX_GAMES
9212                                 - gn - 1] = CMAIL_OLD_RESULT;
9213                 }
9214             }
9215             break;
9216
9217           case NormalMove:
9218             /* Only a NormalMove can be at the start of a game
9219              * without a position diagram. */
9220             if (lastLoadGameStart == (ChessMove) 0) {
9221               gn--;
9222               lastLoadGameStart = MoveNumberOne;
9223             }
9224             break;
9225
9226           default:
9227             break;
9228         }
9229     }
9230     
9231     if (appData.debugMode)
9232       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9233
9234     if (cm == XBoardGame) {
9235         /* Skip any header junk before position diagram and/or move 1 */
9236         for (;;) {
9237             yyboardindex = forwardMostMove;
9238             cm = (ChessMove) yylex();
9239
9240             if (cm == (ChessMove) 0 ||
9241                 cm == GNUChessGame || cm == XBoardGame) {
9242                 /* Empty game; pretend end-of-file and handle later */
9243                 cm = (ChessMove) 0;
9244                 break;
9245             }
9246
9247             if (cm == MoveNumberOne || cm == PositionDiagram ||
9248                 cm == PGNTag || cm == Comment)
9249               break;
9250         }
9251     } else if (cm == GNUChessGame) {
9252         if (gameInfo.event != NULL) {
9253             free(gameInfo.event);
9254         }
9255         gameInfo.event = StrSave(yy_text);
9256     }   
9257
9258     startedFromSetupPosition = FALSE;
9259     while (cm == PGNTag) {
9260         if (appData.debugMode) 
9261           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9262         err = ParsePGNTag(yy_text, &gameInfo);
9263         if (!err) numPGNTags++;
9264
9265         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9266         if(gameInfo.variant != oldVariant) {
9267             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9268             InitPosition(TRUE);
9269             oldVariant = gameInfo.variant;
9270             if (appData.debugMode) 
9271               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9272         }
9273
9274
9275         if (gameInfo.fen != NULL) {
9276           Board initial_position;
9277           startedFromSetupPosition = TRUE;
9278           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9279             Reset(TRUE, TRUE);
9280             DisplayError(_("Bad FEN position in file"), 0);
9281             return FALSE;
9282           }
9283           CopyBoard(boards[0], initial_position);
9284           if (blackPlaysFirst) {
9285             currentMove = forwardMostMove = backwardMostMove = 1;
9286             CopyBoard(boards[1], initial_position);
9287             strcpy(moveList[0], "");
9288             strcpy(parseList[0], "");
9289             timeRemaining[0][1] = whiteTimeRemaining;
9290             timeRemaining[1][1] = blackTimeRemaining;
9291             if (commentList[0] != NULL) {
9292               commentList[1] = commentList[0];
9293               commentList[0] = NULL;
9294             }
9295           } else {
9296             currentMove = forwardMostMove = backwardMostMove = 0;
9297           }
9298           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9299           {   int i;
9300               initialRulePlies = FENrulePlies;
9301               for( i=0; i< nrCastlingRights; i++ )
9302                   initialRights[i] = initial_position[CASTLING][i];
9303           }
9304           yyboardindex = forwardMostMove;
9305           free(gameInfo.fen);
9306           gameInfo.fen = NULL;
9307         }
9308
9309         yyboardindex = forwardMostMove;
9310         cm = (ChessMove) yylex();
9311
9312         /* Handle comments interspersed among the tags */
9313         while (cm == Comment) {
9314             char *p;
9315             if (appData.debugMode) 
9316               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9317             p = yy_text;
9318             AppendComment(currentMove, p, FALSE);
9319             yyboardindex = forwardMostMove;
9320             cm = (ChessMove) yylex();
9321         }
9322     }
9323
9324     /* don't rely on existence of Event tag since if game was
9325      * pasted from clipboard the Event tag may not exist
9326      */
9327     if (numPGNTags > 0){
9328         char *tags;
9329         if (gameInfo.variant == VariantNormal) {
9330           gameInfo.variant = StringToVariant(gameInfo.event);
9331         }
9332         if (!matchMode) {
9333           if( appData.autoDisplayTags ) {
9334             tags = PGNTags(&gameInfo);
9335             TagsPopUp(tags, CmailMsg());
9336             free(tags);
9337           }
9338         }
9339     } else {
9340         /* Make something up, but don't display it now */
9341         SetGameInfo();
9342         TagsPopDown();
9343     }
9344
9345     if (cm == PositionDiagram) {
9346         int i, j;
9347         char *p;
9348         Board initial_position;
9349
9350         if (appData.debugMode)
9351           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9352
9353         if (!startedFromSetupPosition) {
9354             p = yy_text;
9355             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9356               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9357                 switch (*p) {
9358                   case '[':
9359                   case '-':
9360                   case ' ':
9361                   case '\t':
9362                   case '\n':
9363                   case '\r':
9364                     break;
9365                   default:
9366                     initial_position[i][j++] = CharToPiece(*p);
9367                     break;
9368                 }
9369             while (*p == ' ' || *p == '\t' ||
9370                    *p == '\n' || *p == '\r') p++;
9371         
9372             if (strncmp(p, "black", strlen("black"))==0)
9373               blackPlaysFirst = TRUE;
9374             else
9375               blackPlaysFirst = FALSE;
9376             startedFromSetupPosition = TRUE;
9377         
9378             CopyBoard(boards[0], initial_position);
9379             if (blackPlaysFirst) {
9380                 currentMove = forwardMostMove = backwardMostMove = 1;
9381                 CopyBoard(boards[1], initial_position);
9382                 strcpy(moveList[0], "");
9383                 strcpy(parseList[0], "");
9384                 timeRemaining[0][1] = whiteTimeRemaining;
9385                 timeRemaining[1][1] = blackTimeRemaining;
9386                 if (commentList[0] != NULL) {
9387                     commentList[1] = commentList[0];
9388                     commentList[0] = NULL;
9389                 }
9390             } else {
9391                 currentMove = forwardMostMove = backwardMostMove = 0;
9392             }
9393         }
9394         yyboardindex = forwardMostMove;
9395         cm = (ChessMove) yylex();
9396     }
9397
9398     if (first.pr == NoProc) {
9399         StartChessProgram(&first);
9400     }
9401     InitChessProgram(&first, FALSE);
9402     SendToProgram("force\n", &first);
9403     if (startedFromSetupPosition) {
9404         SendBoard(&first, forwardMostMove);
9405     if (appData.debugMode) {
9406         fprintf(debugFP, "Load Game\n");
9407     }
9408         DisplayBothClocks();
9409     }      
9410
9411     /* [HGM] server: flag to write setup moves in broadcast file as one */
9412     loadFlag = appData.suppressLoadMoves;
9413
9414     while (cm == Comment) {
9415         char *p;
9416         if (appData.debugMode) 
9417           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9418         p = yy_text;
9419         AppendComment(currentMove, p, FALSE);
9420         yyboardindex = forwardMostMove;
9421         cm = (ChessMove) yylex();
9422     }
9423
9424     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9425         cm == WhiteWins || cm == BlackWins ||
9426         cm == GameIsDrawn || cm == GameUnfinished) {
9427         DisplayMessage("", _("No moves in game"));
9428         if (cmailMsgLoaded) {
9429             if (appData.debugMode)
9430               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9431             ClearHighlights();
9432             flipView = FALSE;
9433         }
9434         DrawPosition(FALSE, boards[currentMove]);
9435         DisplayBothClocks();
9436         gameMode = EditGame;
9437         ModeHighlight();
9438         gameFileFP = NULL;
9439         cmailOldMove = 0;
9440         return TRUE;
9441     }
9442
9443     // [HGM] PV info: routine tests if comment empty
9444     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9445         DisplayComment(currentMove - 1, commentList[currentMove]);
9446     }
9447     if (!matchMode && appData.timeDelay != 0) 
9448       DrawPosition(FALSE, boards[currentMove]);
9449
9450     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9451       programStats.ok_to_send = 1;
9452     }
9453
9454     /* if the first token after the PGN tags is a move
9455      * and not move number 1, retrieve it from the parser 
9456      */
9457     if (cm != MoveNumberOne)
9458         LoadGameOneMove(cm);
9459
9460     /* load the remaining moves from the file */
9461     while (LoadGameOneMove((ChessMove)0)) {
9462       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9463       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9464     }
9465
9466     /* rewind to the start of the game */
9467     currentMove = backwardMostMove;
9468
9469     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9470
9471     if (oldGameMode == AnalyzeFile ||
9472         oldGameMode == AnalyzeMode) {
9473       AnalyzeFileEvent();
9474     }
9475
9476     if (matchMode || appData.timeDelay == 0) {
9477       ToEndEvent();
9478       gameMode = EditGame;
9479       ModeHighlight();
9480     } else if (appData.timeDelay > 0) {
9481       AutoPlayGameLoop();
9482     }
9483
9484     if (appData.debugMode) 
9485         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9486
9487     loadFlag = 0; /* [HGM] true game starts */
9488     return TRUE;
9489 }
9490
9491 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9492 int
9493 ReloadPosition(offset)
9494      int offset;
9495 {
9496     int positionNumber = lastLoadPositionNumber + offset;
9497     if (lastLoadPositionFP == NULL) {
9498         DisplayError(_("No position has been loaded yet"), 0);
9499         return FALSE;
9500     }
9501     if (positionNumber <= 0) {
9502         DisplayError(_("Can't back up any further"), 0);
9503         return FALSE;
9504     }
9505     return LoadPosition(lastLoadPositionFP, positionNumber,
9506                         lastLoadPositionTitle);
9507 }
9508
9509 /* Load the nth position from the given file */
9510 int
9511 LoadPositionFromFile(filename, n, title)
9512      char *filename;
9513      int n;
9514      char *title;
9515 {
9516     FILE *f;
9517     char buf[MSG_SIZ];
9518
9519     if (strcmp(filename, "-") == 0) {
9520         return LoadPosition(stdin, n, "stdin");
9521     } else {
9522         f = fopen(filename, "rb");
9523         if (f == NULL) {
9524             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9525             DisplayError(buf, errno);
9526             return FALSE;
9527         } else {
9528             return LoadPosition(f, n, title);
9529         }
9530     }
9531 }
9532
9533 /* Load the nth position from the given open file, and close it */
9534 int
9535 LoadPosition(f, positionNumber, title)
9536      FILE *f;
9537      int positionNumber;
9538      char *title;
9539 {
9540     char *p, line[MSG_SIZ];
9541     Board initial_position;
9542     int i, j, fenMode, pn;
9543     
9544     if (gameMode == Training )
9545         SetTrainingModeOff();
9546
9547     if (gameMode != BeginningOfGame) {
9548         Reset(FALSE, TRUE);
9549     }
9550     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9551         fclose(lastLoadPositionFP);
9552     }
9553     if (positionNumber == 0) positionNumber = 1;
9554     lastLoadPositionFP = f;
9555     lastLoadPositionNumber = positionNumber;
9556     strcpy(lastLoadPositionTitle, title);
9557     if (first.pr == NoProc) {
9558       StartChessProgram(&first);
9559       InitChessProgram(&first, FALSE);
9560     }    
9561     pn = positionNumber;
9562     if (positionNumber < 0) {
9563         /* Negative position number means to seek to that byte offset */
9564         if (fseek(f, -positionNumber, 0) == -1) {
9565             DisplayError(_("Can't seek on position file"), 0);
9566             return FALSE;
9567         };
9568         pn = 1;
9569     } else {
9570         if (fseek(f, 0, 0) == -1) {
9571             if (f == lastLoadPositionFP ?
9572                 positionNumber == lastLoadPositionNumber + 1 :
9573                 positionNumber == 1) {
9574                 pn = 1;
9575             } else {
9576                 DisplayError(_("Can't seek on position file"), 0);
9577                 return FALSE;
9578             }
9579         }
9580     }
9581     /* See if this file is FEN or old-style xboard */
9582     if (fgets(line, MSG_SIZ, f) == NULL) {
9583         DisplayError(_("Position not found in file"), 0);
9584         return FALSE;
9585     }
9586     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9587     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9588
9589     if (pn >= 2) {
9590         if (fenMode || line[0] == '#') pn--;
9591         while (pn > 0) {
9592             /* skip positions before number pn */
9593             if (fgets(line, MSG_SIZ, f) == NULL) {
9594                 Reset(TRUE, TRUE);
9595                 DisplayError(_("Position not found in file"), 0);
9596                 return FALSE;
9597             }
9598             if (fenMode || line[0] == '#') pn--;
9599         }
9600     }
9601
9602     if (fenMode) {
9603         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9604             DisplayError(_("Bad FEN position in file"), 0);
9605             return FALSE;
9606         }
9607     } else {
9608         (void) fgets(line, MSG_SIZ, f);
9609         (void) fgets(line, MSG_SIZ, f);
9610     
9611         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9612             (void) fgets(line, MSG_SIZ, f);
9613             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9614                 if (*p == ' ')
9615                   continue;
9616                 initial_position[i][j++] = CharToPiece(*p);
9617             }
9618         }
9619     
9620         blackPlaysFirst = FALSE;
9621         if (!feof(f)) {
9622             (void) fgets(line, MSG_SIZ, f);
9623             if (strncmp(line, "black", strlen("black"))==0)
9624               blackPlaysFirst = TRUE;
9625         }
9626     }
9627     startedFromSetupPosition = TRUE;
9628     
9629     SendToProgram("force\n", &first);
9630     CopyBoard(boards[0], initial_position);
9631     if (blackPlaysFirst) {
9632         currentMove = forwardMostMove = backwardMostMove = 1;
9633         strcpy(moveList[0], "");
9634         strcpy(parseList[0], "");
9635         CopyBoard(boards[1], initial_position);
9636         DisplayMessage("", _("Black to play"));
9637     } else {
9638         currentMove = forwardMostMove = backwardMostMove = 0;
9639         DisplayMessage("", _("White to play"));
9640     }
9641     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9642     SendBoard(&first, forwardMostMove);
9643     if (appData.debugMode) {
9644 int i, j;
9645   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9646   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9647         fprintf(debugFP, "Load Position\n");
9648     }
9649
9650     if (positionNumber > 1) {
9651         sprintf(line, "%s %d", title, positionNumber);
9652         DisplayTitle(line);
9653     } else {
9654         DisplayTitle(title);
9655     }
9656     gameMode = EditGame;
9657     ModeHighlight();
9658     ResetClocks();
9659     timeRemaining[0][1] = whiteTimeRemaining;
9660     timeRemaining[1][1] = blackTimeRemaining;
9661     DrawPosition(FALSE, boards[currentMove]);
9662    
9663     return TRUE;
9664 }
9665
9666
9667 void
9668 CopyPlayerNameIntoFileName(dest, src)
9669      char **dest, *src;
9670 {
9671     while (*src != NULLCHAR && *src != ',') {
9672         if (*src == ' ') {
9673             *(*dest)++ = '_';
9674             src++;
9675         } else {
9676             *(*dest)++ = *src++;
9677         }
9678     }
9679 }
9680
9681 char *DefaultFileName(ext)
9682      char *ext;
9683 {
9684     static char def[MSG_SIZ];
9685     char *p;
9686
9687     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9688         p = def;
9689         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9690         *p++ = '-';
9691         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9692         *p++ = '.';
9693         strcpy(p, ext);
9694     } else {
9695         def[0] = NULLCHAR;
9696     }
9697     return def;
9698 }
9699
9700 /* Save the current game to the given file */
9701 int
9702 SaveGameToFile(filename, append)
9703      char *filename;
9704      int append;
9705 {
9706     FILE *f;
9707     char buf[MSG_SIZ];
9708
9709     if (strcmp(filename, "-") == 0) {
9710         return SaveGame(stdout, 0, NULL);
9711     } else {
9712         f = fopen(filename, append ? "a" : "w");
9713         if (f == NULL) {
9714             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9715             DisplayError(buf, errno);
9716             return FALSE;
9717         } else {
9718             return SaveGame(f, 0, NULL);
9719         }
9720     }
9721 }
9722
9723 char *
9724 SavePart(str)
9725      char *str;
9726 {
9727     static char buf[MSG_SIZ];
9728     char *p;
9729     
9730     p = strchr(str, ' ');
9731     if (p == NULL) return str;
9732     strncpy(buf, str, p - str);
9733     buf[p - str] = NULLCHAR;
9734     return buf;
9735 }
9736
9737 #define PGN_MAX_LINE 75
9738
9739 #define PGN_SIDE_WHITE  0
9740 #define PGN_SIDE_BLACK  1
9741
9742 /* [AS] */
9743 static int FindFirstMoveOutOfBook( int side )
9744 {
9745     int result = -1;
9746
9747     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9748         int index = backwardMostMove;
9749         int has_book_hit = 0;
9750
9751         if( (index % 2) != side ) {
9752             index++;
9753         }
9754
9755         while( index < forwardMostMove ) {
9756             /* Check to see if engine is in book */
9757             int depth = pvInfoList[index].depth;
9758             int score = pvInfoList[index].score;
9759             int in_book = 0;
9760
9761             if( depth <= 2 ) {
9762                 in_book = 1;
9763             }
9764             else if( score == 0 && depth == 63 ) {
9765                 in_book = 1; /* Zappa */
9766             }
9767             else if( score == 2 && depth == 99 ) {
9768                 in_book = 1; /* Abrok */
9769             }
9770
9771             has_book_hit += in_book;
9772
9773             if( ! in_book ) {
9774                 result = index;
9775
9776                 break;
9777             }
9778
9779             index += 2;
9780         }
9781     }
9782
9783     return result;
9784 }
9785
9786 /* [AS] */
9787 void GetOutOfBookInfo( char * buf )
9788 {
9789     int oob[2];
9790     int i;
9791     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9792
9793     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9794     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9795
9796     *buf = '\0';
9797
9798     if( oob[0] >= 0 || oob[1] >= 0 ) {
9799         for( i=0; i<2; i++ ) {
9800             int idx = oob[i];
9801
9802             if( idx >= 0 ) {
9803                 if( i > 0 && oob[0] >= 0 ) {
9804                     strcat( buf, "   " );
9805                 }
9806
9807                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9808                 sprintf( buf+strlen(buf), "%s%.2f", 
9809                     pvInfoList[idx].score >= 0 ? "+" : "",
9810                     pvInfoList[idx].score / 100.0 );
9811             }
9812         }
9813     }
9814 }
9815
9816 /* Save game in PGN style and close the file */
9817 int
9818 SaveGamePGN(f)
9819      FILE *f;
9820 {
9821     int i, offset, linelen, newblock;
9822     time_t tm;
9823 //    char *movetext;
9824     char numtext[32];
9825     int movelen, numlen, blank;
9826     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9827
9828     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9829     
9830     tm = time((time_t *) NULL);
9831     
9832     PrintPGNTags(f, &gameInfo);
9833     
9834     if (backwardMostMove > 0 || startedFromSetupPosition) {
9835         char *fen = PositionToFEN(backwardMostMove, NULL);
9836         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9837         fprintf(f, "\n{--------------\n");
9838         PrintPosition(f, backwardMostMove);
9839         fprintf(f, "--------------}\n");
9840         free(fen);
9841     }
9842     else {
9843         /* [AS] Out of book annotation */
9844         if( appData.saveOutOfBookInfo ) {
9845             char buf[64];
9846
9847             GetOutOfBookInfo( buf );
9848
9849             if( buf[0] != '\0' ) {
9850                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9851             }
9852         }
9853
9854         fprintf(f, "\n");
9855     }
9856
9857     i = backwardMostMove;
9858     linelen = 0;
9859     newblock = TRUE;
9860
9861     while (i < forwardMostMove) {
9862         /* Print comments preceding this move */
9863         if (commentList[i] != NULL) {
9864             if (linelen > 0) fprintf(f, "\n");
9865             fprintf(f, "%s", commentList[i]);
9866             linelen = 0;
9867             newblock = TRUE;
9868         }
9869
9870         /* Format move number */
9871         if ((i % 2) == 0) {
9872             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9873         } else {
9874             if (newblock) {
9875                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9876             } else {
9877                 numtext[0] = NULLCHAR;
9878             }
9879         }
9880         numlen = strlen(numtext);
9881         newblock = FALSE;
9882
9883         /* Print move number */
9884         blank = linelen > 0 && numlen > 0;
9885         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9886             fprintf(f, "\n");
9887             linelen = 0;
9888             blank = 0;
9889         }
9890         if (blank) {
9891             fprintf(f, " ");
9892             linelen++;
9893         }
9894         fprintf(f, "%s", numtext);
9895         linelen += numlen;
9896
9897         /* Get move */
9898         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9899         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9900
9901         /* Print move */
9902         blank = linelen > 0 && movelen > 0;
9903         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9904             fprintf(f, "\n");
9905             linelen = 0;
9906             blank = 0;
9907         }
9908         if (blank) {
9909             fprintf(f, " ");
9910             linelen++;
9911         }
9912         fprintf(f, "%s", move_buffer);
9913         linelen += movelen;
9914
9915         /* [AS] Add PV info if present */
9916         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9917             /* [HGM] add time */
9918             char buf[MSG_SIZ]; int seconds;
9919
9920             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9921
9922             if( seconds <= 0) buf[0] = 0; else
9923             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9924                 seconds = (seconds + 4)/10; // round to full seconds
9925                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9926                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9927             }
9928
9929             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9930                 pvInfoList[i].score >= 0 ? "+" : "",
9931                 pvInfoList[i].score / 100.0,
9932                 pvInfoList[i].depth,
9933                 buf );
9934
9935             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9936
9937             /* Print score/depth */
9938             blank = linelen > 0 && movelen > 0;
9939             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9940                 fprintf(f, "\n");
9941                 linelen = 0;
9942                 blank = 0;
9943             }
9944             if (blank) {
9945                 fprintf(f, " ");
9946                 linelen++;
9947             }
9948             fprintf(f, "%s", move_buffer);
9949             linelen += movelen;
9950         }
9951
9952         i++;
9953     }
9954     
9955     /* Start a new line */
9956     if (linelen > 0) fprintf(f, "\n");
9957
9958     /* Print comments after last move */
9959     if (commentList[i] != NULL) {
9960         fprintf(f, "%s\n", commentList[i]);
9961     }
9962
9963     /* Print result */
9964     if (gameInfo.resultDetails != NULL &&
9965         gameInfo.resultDetails[0] != NULLCHAR) {
9966         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9967                 PGNResult(gameInfo.result));
9968     } else {
9969         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9970     }
9971
9972     fclose(f);
9973     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9974     return TRUE;
9975 }
9976
9977 /* Save game in old style and close the file */
9978 int
9979 SaveGameOldStyle(f)
9980      FILE *f;
9981 {
9982     int i, offset;
9983     time_t tm;
9984     
9985     tm = time((time_t *) NULL);
9986     
9987     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9988     PrintOpponents(f);
9989     
9990     if (backwardMostMove > 0 || startedFromSetupPosition) {
9991         fprintf(f, "\n[--------------\n");
9992         PrintPosition(f, backwardMostMove);
9993         fprintf(f, "--------------]\n");
9994     } else {
9995         fprintf(f, "\n");
9996     }
9997
9998     i = backwardMostMove;
9999     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10000
10001     while (i < forwardMostMove) {
10002         if (commentList[i] != NULL) {
10003             fprintf(f, "[%s]\n", commentList[i]);
10004         }
10005
10006         if ((i % 2) == 1) {
10007             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10008             i++;
10009         } else {
10010             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10011             i++;
10012             if (commentList[i] != NULL) {
10013                 fprintf(f, "\n");
10014                 continue;
10015             }
10016             if (i >= forwardMostMove) {
10017                 fprintf(f, "\n");
10018                 break;
10019             }
10020             fprintf(f, "%s\n", parseList[i]);
10021             i++;
10022         }
10023     }
10024     
10025     if (commentList[i] != NULL) {
10026         fprintf(f, "[%s]\n", commentList[i]);
10027     }
10028
10029     /* This isn't really the old style, but it's close enough */
10030     if (gameInfo.resultDetails != NULL &&
10031         gameInfo.resultDetails[0] != NULLCHAR) {
10032         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10033                 gameInfo.resultDetails);
10034     } else {
10035         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10036     }
10037
10038     fclose(f);
10039     return TRUE;
10040 }
10041
10042 /* Save the current game to open file f and close the file */
10043 int
10044 SaveGame(f, dummy, dummy2)
10045      FILE *f;
10046      int dummy;
10047      char *dummy2;
10048 {
10049     if (gameMode == EditPosition) EditPositionDone(TRUE);
10050     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10051     if (appData.oldSaveStyle)
10052       return SaveGameOldStyle(f);
10053     else
10054       return SaveGamePGN(f);
10055 }
10056
10057 /* Save the current position to the given file */
10058 int
10059 SavePositionToFile(filename)
10060      char *filename;
10061 {
10062     FILE *f;
10063     char buf[MSG_SIZ];
10064
10065     if (strcmp(filename, "-") == 0) {
10066         return SavePosition(stdout, 0, NULL);
10067     } else {
10068         f = fopen(filename, "a");
10069         if (f == NULL) {
10070             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10071             DisplayError(buf, errno);
10072             return FALSE;
10073         } else {
10074             SavePosition(f, 0, NULL);
10075             return TRUE;
10076         }
10077     }
10078 }
10079
10080 /* Save the current position to the given open file and close the file */
10081 int
10082 SavePosition(f, dummy, dummy2)
10083      FILE *f;
10084      int dummy;
10085      char *dummy2;
10086 {
10087     time_t tm;
10088     char *fen;
10089     
10090     if (gameMode == EditPosition) EditPositionDone(TRUE);
10091     if (appData.oldSaveStyle) {
10092         tm = time((time_t *) NULL);
10093     
10094         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10095         PrintOpponents(f);
10096         fprintf(f, "[--------------\n");
10097         PrintPosition(f, currentMove);
10098         fprintf(f, "--------------]\n");
10099     } else {
10100         fen = PositionToFEN(currentMove, NULL);
10101         fprintf(f, "%s\n", fen);
10102         free(fen);
10103     }
10104     fclose(f);
10105     return TRUE;
10106 }
10107
10108 void
10109 ReloadCmailMsgEvent(unregister)
10110      int unregister;
10111 {
10112 #if !WIN32
10113     static char *inFilename = NULL;
10114     static char *outFilename;
10115     int i;
10116     struct stat inbuf, outbuf;
10117     int status;
10118     
10119     /* Any registered moves are unregistered if unregister is set, */
10120     /* i.e. invoked by the signal handler */
10121     if (unregister) {
10122         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10123             cmailMoveRegistered[i] = FALSE;
10124             if (cmailCommentList[i] != NULL) {
10125                 free(cmailCommentList[i]);
10126                 cmailCommentList[i] = NULL;
10127             }
10128         }
10129         nCmailMovesRegistered = 0;
10130     }
10131
10132     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10133         cmailResult[i] = CMAIL_NOT_RESULT;
10134     }
10135     nCmailResults = 0;
10136
10137     if (inFilename == NULL) {
10138         /* Because the filenames are static they only get malloced once  */
10139         /* and they never get freed                                      */
10140         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10141         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10142
10143         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10144         sprintf(outFilename, "%s.out", appData.cmailGameName);
10145     }
10146     
10147     status = stat(outFilename, &outbuf);
10148     if (status < 0) {
10149         cmailMailedMove = FALSE;
10150     } else {
10151         status = stat(inFilename, &inbuf);
10152         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10153     }
10154     
10155     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10156        counts the games, notes how each one terminated, etc.
10157        
10158        It would be nice to remove this kludge and instead gather all
10159        the information while building the game list.  (And to keep it
10160        in the game list nodes instead of having a bunch of fixed-size
10161        parallel arrays.)  Note this will require getting each game's
10162        termination from the PGN tags, as the game list builder does
10163        not process the game moves.  --mann
10164        */
10165     cmailMsgLoaded = TRUE;
10166     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10167     
10168     /* Load first game in the file or popup game menu */
10169     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10170
10171 #endif /* !WIN32 */
10172     return;
10173 }
10174
10175 int
10176 RegisterMove()
10177 {
10178     FILE *f;
10179     char string[MSG_SIZ];
10180
10181     if (   cmailMailedMove
10182         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10183         return TRUE;            /* Allow free viewing  */
10184     }
10185
10186     /* Unregister move to ensure that we don't leave RegisterMove        */
10187     /* with the move registered when the conditions for registering no   */
10188     /* longer hold                                                       */
10189     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10190         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10191         nCmailMovesRegistered --;
10192
10193         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10194           {
10195               free(cmailCommentList[lastLoadGameNumber - 1]);
10196               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10197           }
10198     }
10199
10200     if (cmailOldMove == -1) {
10201         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10202         return FALSE;
10203     }
10204
10205     if (currentMove > cmailOldMove + 1) {
10206         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10207         return FALSE;
10208     }
10209
10210     if (currentMove < cmailOldMove) {
10211         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10212         return FALSE;
10213     }
10214
10215     if (forwardMostMove > currentMove) {
10216         /* Silently truncate extra moves */
10217         TruncateGame();
10218     }
10219
10220     if (   (currentMove == cmailOldMove + 1)
10221         || (   (currentMove == cmailOldMove)
10222             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10223                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10224         if (gameInfo.result != GameUnfinished) {
10225             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10226         }
10227
10228         if (commentList[currentMove] != NULL) {
10229             cmailCommentList[lastLoadGameNumber - 1]
10230               = StrSave(commentList[currentMove]);
10231         }
10232         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10233
10234         if (appData.debugMode)
10235           fprintf(debugFP, "Saving %s for game %d\n",
10236                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10237
10238         sprintf(string,
10239                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10240         
10241         f = fopen(string, "w");
10242         if (appData.oldSaveStyle) {
10243             SaveGameOldStyle(f); /* also closes the file */
10244             
10245             sprintf(string, "%s.pos.out", appData.cmailGameName);
10246             f = fopen(string, "w");
10247             SavePosition(f, 0, NULL); /* also closes the file */
10248         } else {
10249             fprintf(f, "{--------------\n");
10250             PrintPosition(f, currentMove);
10251             fprintf(f, "--------------}\n\n");
10252             
10253             SaveGame(f, 0, NULL); /* also closes the file*/
10254         }
10255         
10256         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10257         nCmailMovesRegistered ++;
10258     } else if (nCmailGames == 1) {
10259         DisplayError(_("You have not made a move yet"), 0);
10260         return FALSE;
10261     }
10262
10263     return TRUE;
10264 }
10265
10266 void
10267 MailMoveEvent()
10268 {
10269 #if !WIN32
10270     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10271     FILE *commandOutput;
10272     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10273     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10274     int nBuffers;
10275     int i;
10276     int archived;
10277     char *arcDir;
10278
10279     if (! cmailMsgLoaded) {
10280         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10281         return;
10282     }
10283
10284     if (nCmailGames == nCmailResults) {
10285         DisplayError(_("No unfinished games"), 0);
10286         return;
10287     }
10288
10289 #if CMAIL_PROHIBIT_REMAIL
10290     if (cmailMailedMove) {
10291         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);
10292         DisplayError(msg, 0);
10293         return;
10294     }
10295 #endif
10296
10297     if (! (cmailMailedMove || RegisterMove())) return;
10298     
10299     if (   cmailMailedMove
10300         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10301         sprintf(string, partCommandString,
10302                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10303         commandOutput = popen(string, "r");
10304
10305         if (commandOutput == NULL) {
10306             DisplayError(_("Failed to invoke cmail"), 0);
10307         } else {
10308             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10309                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10310             }
10311             if (nBuffers > 1) {
10312                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10313                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10314                 nBytes = MSG_SIZ - 1;
10315             } else {
10316                 (void) memcpy(msg, buffer, nBytes);
10317             }
10318             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10319
10320             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10321                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10322
10323                 archived = TRUE;
10324                 for (i = 0; i < nCmailGames; i ++) {
10325                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10326                         archived = FALSE;
10327                     }
10328                 }
10329                 if (   archived
10330                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10331                         != NULL)) {
10332                     sprintf(buffer, "%s/%s.%s.archive",
10333                             arcDir,
10334                             appData.cmailGameName,
10335                             gameInfo.date);
10336                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10337                     cmailMsgLoaded = FALSE;
10338                 }
10339             }
10340
10341             DisplayInformation(msg);
10342             pclose(commandOutput);
10343         }
10344     } else {
10345         if ((*cmailMsg) != '\0') {
10346             DisplayInformation(cmailMsg);
10347         }
10348     }
10349
10350     return;
10351 #endif /* !WIN32 */
10352 }
10353
10354 char *
10355 CmailMsg()
10356 {
10357 #if WIN32
10358     return NULL;
10359 #else
10360     int  prependComma = 0;
10361     char number[5];
10362     char string[MSG_SIZ];       /* Space for game-list */
10363     int  i;
10364     
10365     if (!cmailMsgLoaded) return "";
10366
10367     if (cmailMailedMove) {
10368         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10369     } else {
10370         /* Create a list of games left */
10371         sprintf(string, "[");
10372         for (i = 0; i < nCmailGames; i ++) {
10373             if (! (   cmailMoveRegistered[i]
10374                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10375                 if (prependComma) {
10376                     sprintf(number, ",%d", i + 1);
10377                 } else {
10378                     sprintf(number, "%d", i + 1);
10379                     prependComma = 1;
10380                 }
10381                 
10382                 strcat(string, number);
10383             }
10384         }
10385         strcat(string, "]");
10386
10387         if (nCmailMovesRegistered + nCmailResults == 0) {
10388             switch (nCmailGames) {
10389               case 1:
10390                 sprintf(cmailMsg,
10391                         _("Still need to make move for game\n"));
10392                 break;
10393                 
10394               case 2:
10395                 sprintf(cmailMsg,
10396                         _("Still need to make moves for both games\n"));
10397                 break;
10398                 
10399               default:
10400                 sprintf(cmailMsg,
10401                         _("Still need to make moves for all %d games\n"),
10402                         nCmailGames);
10403                 break;
10404             }
10405         } else {
10406             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10407               case 1:
10408                 sprintf(cmailMsg,
10409                         _("Still need to make a move for game %s\n"),
10410                         string);
10411                 break;
10412                 
10413               case 0:
10414                 if (nCmailResults == nCmailGames) {
10415                     sprintf(cmailMsg, _("No unfinished games\n"));
10416                 } else {
10417                     sprintf(cmailMsg, _("Ready to send mail\n"));
10418                 }
10419                 break;
10420                 
10421               default:
10422                 sprintf(cmailMsg,
10423                         _("Still need to make moves for games %s\n"),
10424                         string);
10425             }
10426         }
10427     }
10428     return cmailMsg;
10429 #endif /* WIN32 */
10430 }
10431
10432 void
10433 ResetGameEvent()
10434 {
10435     if (gameMode == Training)
10436       SetTrainingModeOff();
10437
10438     Reset(TRUE, TRUE);
10439     cmailMsgLoaded = FALSE;
10440     if (appData.icsActive) {
10441       SendToICS(ics_prefix);
10442       SendToICS("refresh\n");
10443     }
10444 }
10445
10446 void
10447 ExitEvent(status)
10448      int status;
10449 {
10450     exiting++;
10451     if (exiting > 2) {
10452       /* Give up on clean exit */
10453       exit(status);
10454     }
10455     if (exiting > 1) {
10456       /* Keep trying for clean exit */
10457       return;
10458     }
10459
10460     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10461
10462     if (telnetISR != NULL) {
10463       RemoveInputSource(telnetISR);
10464     }
10465     if (icsPR != NoProc) {
10466       DestroyChildProcess(icsPR, TRUE);
10467     }
10468
10469     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10470     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10471
10472     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10473     /* make sure this other one finishes before killing it!                  */
10474     if(endingGame) { int count = 0;
10475         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10476         while(endingGame && count++ < 10) DoSleep(1);
10477         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10478     }
10479
10480     /* Kill off chess programs */
10481     if (first.pr != NoProc) {
10482         ExitAnalyzeMode();
10483         
10484         DoSleep( appData.delayBeforeQuit );
10485         SendToProgram("quit\n", &first);
10486         DoSleep( appData.delayAfterQuit );
10487         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10488     }
10489     if (second.pr != NoProc) {
10490         DoSleep( appData.delayBeforeQuit );
10491         SendToProgram("quit\n", &second);
10492         DoSleep( appData.delayAfterQuit );
10493         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10494     }
10495     if (first.isr != NULL) {
10496         RemoveInputSource(first.isr);
10497     }
10498     if (second.isr != NULL) {
10499         RemoveInputSource(second.isr);
10500     }
10501
10502     ShutDownFrontEnd();
10503     exit(status);
10504 }
10505
10506 void
10507 PauseEvent()
10508 {
10509     if (appData.debugMode)
10510         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10511     if (pausing) {
10512         pausing = FALSE;
10513         ModeHighlight();
10514         if (gameMode == MachinePlaysWhite ||
10515             gameMode == MachinePlaysBlack) {
10516             StartClocks();
10517         } else {
10518             DisplayBothClocks();
10519         }
10520         if (gameMode == PlayFromGameFile) {
10521             if (appData.timeDelay >= 0) 
10522                 AutoPlayGameLoop();
10523         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10524             Reset(FALSE, TRUE);
10525             SendToICS(ics_prefix);
10526             SendToICS("refresh\n");
10527         } else if (currentMove < forwardMostMove) {
10528             ForwardInner(forwardMostMove);
10529         }
10530         pauseExamInvalid = FALSE;
10531     } else {
10532         switch (gameMode) {
10533           default:
10534             return;
10535           case IcsExamining:
10536             pauseExamForwardMostMove = forwardMostMove;
10537             pauseExamInvalid = FALSE;
10538             /* fall through */
10539           case IcsObserving:
10540           case IcsPlayingWhite:
10541           case IcsPlayingBlack:
10542             pausing = TRUE;
10543             ModeHighlight();
10544             return;
10545           case PlayFromGameFile:
10546             (void) StopLoadGameTimer();
10547             pausing = TRUE;
10548             ModeHighlight();
10549             break;
10550           case BeginningOfGame:
10551             if (appData.icsActive) return;
10552             /* else fall through */
10553           case MachinePlaysWhite:
10554           case MachinePlaysBlack:
10555           case TwoMachinesPlay:
10556             if (forwardMostMove == 0)
10557               return;           /* don't pause if no one has moved */
10558             if ((gameMode == MachinePlaysWhite &&
10559                  !WhiteOnMove(forwardMostMove)) ||
10560                 (gameMode == MachinePlaysBlack &&
10561                  WhiteOnMove(forwardMostMove))) {
10562                 StopClocks();
10563             }
10564             pausing = TRUE;
10565             ModeHighlight();
10566             break;
10567         }
10568     }
10569 }
10570
10571 void
10572 EditCommentEvent()
10573 {
10574     char title[MSG_SIZ];
10575
10576     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10577         strcpy(title, _("Edit comment"));
10578     } else {
10579         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10580                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10581                 parseList[currentMove - 1]);
10582     }
10583
10584     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10585 }
10586
10587
10588 void
10589 EditTagsEvent()
10590 {
10591     char *tags = PGNTags(&gameInfo);
10592     EditTagsPopUp(tags);
10593     free(tags);
10594 }
10595
10596 void
10597 AnalyzeModeEvent()
10598 {
10599     if (appData.noChessProgram || gameMode == AnalyzeMode)
10600       return;
10601
10602     if (gameMode != AnalyzeFile) {
10603         if (!appData.icsEngineAnalyze) {
10604                EditGameEvent();
10605                if (gameMode != EditGame) return;
10606         }
10607         ResurrectChessProgram();
10608         SendToProgram("analyze\n", &first);
10609         first.analyzing = TRUE;
10610         /*first.maybeThinking = TRUE;*/
10611         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10612         EngineOutputPopUp();
10613     }
10614     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10615     pausing = FALSE;
10616     ModeHighlight();
10617     SetGameInfo();
10618
10619     StartAnalysisClock();
10620     GetTimeMark(&lastNodeCountTime);
10621     lastNodeCount = 0;
10622 }
10623
10624 void
10625 AnalyzeFileEvent()
10626 {
10627     if (appData.noChessProgram || gameMode == AnalyzeFile)
10628       return;
10629
10630     if (gameMode != AnalyzeMode) {
10631         EditGameEvent();
10632         if (gameMode != EditGame) return;
10633         ResurrectChessProgram();
10634         SendToProgram("analyze\n", &first);
10635         first.analyzing = TRUE;
10636         /*first.maybeThinking = TRUE;*/
10637         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10638         EngineOutputPopUp();
10639     }
10640     gameMode = AnalyzeFile;
10641     pausing = FALSE;
10642     ModeHighlight();
10643     SetGameInfo();
10644
10645     StartAnalysisClock();
10646     GetTimeMark(&lastNodeCountTime);
10647     lastNodeCount = 0;
10648 }
10649
10650 void
10651 MachineWhiteEvent()
10652 {
10653     char buf[MSG_SIZ];
10654     char *bookHit = NULL;
10655
10656     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10657       return;
10658
10659
10660     if (gameMode == PlayFromGameFile || 
10661         gameMode == TwoMachinesPlay  || 
10662         gameMode == Training         || 
10663         gameMode == AnalyzeMode      || 
10664         gameMode == EndOfGame)
10665         EditGameEvent();
10666
10667     if (gameMode == EditPosition) 
10668         EditPositionDone(TRUE);
10669
10670     if (!WhiteOnMove(currentMove)) {
10671         DisplayError(_("It is not White's turn"), 0);
10672         return;
10673     }
10674   
10675     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10676       ExitAnalyzeMode();
10677
10678     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10679         gameMode == AnalyzeFile)
10680         TruncateGame();
10681
10682     ResurrectChessProgram();    /* in case it isn't running */
10683     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10684         gameMode = MachinePlaysWhite;
10685         ResetClocks();
10686     } else
10687     gameMode = MachinePlaysWhite;
10688     pausing = FALSE;
10689     ModeHighlight();
10690     SetGameInfo();
10691     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10692     DisplayTitle(buf);
10693     if (first.sendName) {
10694       sprintf(buf, "name %s\n", gameInfo.black);
10695       SendToProgram(buf, &first);
10696     }
10697     if (first.sendTime) {
10698       if (first.useColors) {
10699         SendToProgram("black\n", &first); /*gnu kludge*/
10700       }
10701       SendTimeRemaining(&first, TRUE);
10702     }
10703     if (first.useColors) {
10704       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10705     }
10706     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10707     SetMachineThinkingEnables();
10708     first.maybeThinking = TRUE;
10709     StartClocks();
10710     firstMove = FALSE;
10711
10712     if (appData.autoFlipView && !flipView) {
10713       flipView = !flipView;
10714       DrawPosition(FALSE, NULL);
10715       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10716     }
10717
10718     if(bookHit) { // [HGM] book: simulate book reply
10719         static char bookMove[MSG_SIZ]; // a bit generous?
10720
10721         programStats.nodes = programStats.depth = programStats.time = 
10722         programStats.score = programStats.got_only_move = 0;
10723         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10724
10725         strcpy(bookMove, "move ");
10726         strcat(bookMove, bookHit);
10727         HandleMachineMove(bookMove, &first);
10728     }
10729 }
10730
10731 void
10732 MachineBlackEvent()
10733 {
10734     char buf[MSG_SIZ];
10735    char *bookHit = NULL;
10736
10737     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10738         return;
10739
10740
10741     if (gameMode == PlayFromGameFile || 
10742         gameMode == TwoMachinesPlay  || 
10743         gameMode == Training         || 
10744         gameMode == AnalyzeMode      || 
10745         gameMode == EndOfGame)
10746         EditGameEvent();
10747
10748     if (gameMode == EditPosition) 
10749         EditPositionDone(TRUE);
10750
10751     if (WhiteOnMove(currentMove)) {
10752         DisplayError(_("It is not Black's turn"), 0);
10753         return;
10754     }
10755     
10756     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10757       ExitAnalyzeMode();
10758
10759     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10760         gameMode == AnalyzeFile)
10761         TruncateGame();
10762
10763     ResurrectChessProgram();    /* in case it isn't running */
10764     gameMode = MachinePlaysBlack;
10765     pausing = FALSE;
10766     ModeHighlight();
10767     SetGameInfo();
10768     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10769     DisplayTitle(buf);
10770     if (first.sendName) {
10771       sprintf(buf, "name %s\n", gameInfo.white);
10772       SendToProgram(buf, &first);
10773     }
10774     if (first.sendTime) {
10775       if (first.useColors) {
10776         SendToProgram("white\n", &first); /*gnu kludge*/
10777       }
10778       SendTimeRemaining(&first, FALSE);
10779     }
10780     if (first.useColors) {
10781       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10782     }
10783     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10784     SetMachineThinkingEnables();
10785     first.maybeThinking = TRUE;
10786     StartClocks();
10787
10788     if (appData.autoFlipView && flipView) {
10789       flipView = !flipView;
10790       DrawPosition(FALSE, NULL);
10791       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10792     }
10793     if(bookHit) { // [HGM] book: simulate book reply
10794         static char bookMove[MSG_SIZ]; // a bit generous?
10795
10796         programStats.nodes = programStats.depth = programStats.time = 
10797         programStats.score = programStats.got_only_move = 0;
10798         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10799
10800         strcpy(bookMove, "move ");
10801         strcat(bookMove, bookHit);
10802         HandleMachineMove(bookMove, &first);
10803     }
10804 }
10805
10806
10807 void
10808 DisplayTwoMachinesTitle()
10809 {
10810     char buf[MSG_SIZ];
10811     if (appData.matchGames > 0) {
10812         if (first.twoMachinesColor[0] == 'w') {
10813             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10814                     gameInfo.white, gameInfo.black,
10815                     first.matchWins, second.matchWins,
10816                     matchGame - 1 - (first.matchWins + second.matchWins));
10817         } else {
10818             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10819                     gameInfo.white, gameInfo.black,
10820                     second.matchWins, first.matchWins,
10821                     matchGame - 1 - (first.matchWins + second.matchWins));
10822         }
10823     } else {
10824         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10825     }
10826     DisplayTitle(buf);
10827 }
10828
10829 void
10830 TwoMachinesEvent P((void))
10831 {
10832     int i;
10833     char buf[MSG_SIZ];
10834     ChessProgramState *onmove;
10835     char *bookHit = NULL;
10836     
10837     if (appData.noChessProgram) return;
10838
10839     switch (gameMode) {
10840       case TwoMachinesPlay:
10841         return;
10842       case MachinePlaysWhite:
10843       case MachinePlaysBlack:
10844         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10845             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10846             return;
10847         }
10848         /* fall through */
10849       case BeginningOfGame:
10850       case PlayFromGameFile:
10851       case EndOfGame:
10852         EditGameEvent();
10853         if (gameMode != EditGame) return;
10854         break;
10855       case EditPosition:
10856         EditPositionDone(TRUE);
10857         break;
10858       case AnalyzeMode:
10859       case AnalyzeFile:
10860         ExitAnalyzeMode();
10861         break;
10862       case EditGame:
10863       default:
10864         break;
10865     }
10866
10867 //    forwardMostMove = currentMove;
10868     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10869     ResurrectChessProgram();    /* in case first program isn't running */
10870
10871     if (second.pr == NULL) {
10872         StartChessProgram(&second);
10873         if (second.protocolVersion == 1) {
10874           TwoMachinesEventIfReady();
10875         } else {
10876           /* kludge: allow timeout for initial "feature" command */
10877           FreezeUI();
10878           DisplayMessage("", _("Starting second chess program"));
10879           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10880         }
10881         return;
10882     }
10883     DisplayMessage("", "");
10884     InitChessProgram(&second, FALSE);
10885     SendToProgram("force\n", &second);
10886     if (startedFromSetupPosition) {
10887         SendBoard(&second, backwardMostMove);
10888     if (appData.debugMode) {
10889         fprintf(debugFP, "Two Machines\n");
10890     }
10891     }
10892     for (i = backwardMostMove; i < forwardMostMove; i++) {
10893         SendMoveToProgram(i, &second);
10894     }
10895
10896     gameMode = TwoMachinesPlay;
10897     pausing = FALSE;
10898     ModeHighlight();
10899     SetGameInfo();
10900     DisplayTwoMachinesTitle();
10901     firstMove = TRUE;
10902     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10903         onmove = &first;
10904     } else {
10905         onmove = &second;
10906     }
10907
10908     SendToProgram(first.computerString, &first);
10909     if (first.sendName) {
10910       sprintf(buf, "name %s\n", second.tidy);
10911       SendToProgram(buf, &first);
10912     }
10913     SendToProgram(second.computerString, &second);
10914     if (second.sendName) {
10915       sprintf(buf, "name %s\n", first.tidy);
10916       SendToProgram(buf, &second);
10917     }
10918
10919     ResetClocks();
10920     if (!first.sendTime || !second.sendTime) {
10921         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10922         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10923     }
10924     if (onmove->sendTime) {
10925       if (onmove->useColors) {
10926         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10927       }
10928       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10929     }
10930     if (onmove->useColors) {
10931       SendToProgram(onmove->twoMachinesColor, onmove);
10932     }
10933     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10934 //    SendToProgram("go\n", onmove);
10935     onmove->maybeThinking = TRUE;
10936     SetMachineThinkingEnables();
10937
10938     StartClocks();
10939
10940     if(bookHit) { // [HGM] book: simulate book reply
10941         static char bookMove[MSG_SIZ]; // a bit generous?
10942
10943         programStats.nodes = programStats.depth = programStats.time = 
10944         programStats.score = programStats.got_only_move = 0;
10945         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10946
10947         strcpy(bookMove, "move ");
10948         strcat(bookMove, bookHit);
10949         savedMessage = bookMove; // args for deferred call
10950         savedState = onmove;
10951         ScheduleDelayedEvent(DeferredBookMove, 1);
10952     }
10953 }
10954
10955 void
10956 TrainingEvent()
10957 {
10958     if (gameMode == Training) {
10959       SetTrainingModeOff();
10960       gameMode = PlayFromGameFile;
10961       DisplayMessage("", _("Training mode off"));
10962     } else {
10963       gameMode = Training;
10964       animateTraining = appData.animate;
10965
10966       /* make sure we are not already at the end of the game */
10967       if (currentMove < forwardMostMove) {
10968         SetTrainingModeOn();
10969         DisplayMessage("", _("Training mode on"));
10970       } else {
10971         gameMode = PlayFromGameFile;
10972         DisplayError(_("Already at end of game"), 0);
10973       }
10974     }
10975     ModeHighlight();
10976 }
10977
10978 void
10979 IcsClientEvent()
10980 {
10981     if (!appData.icsActive) return;
10982     switch (gameMode) {
10983       case IcsPlayingWhite:
10984       case IcsPlayingBlack:
10985       case IcsObserving:
10986       case IcsIdle:
10987       case BeginningOfGame:
10988       case IcsExamining:
10989         return;
10990
10991       case EditGame:
10992         break;
10993
10994       case EditPosition:
10995         EditPositionDone(TRUE);
10996         break;
10997
10998       case AnalyzeMode:
10999       case AnalyzeFile:
11000         ExitAnalyzeMode();
11001         break;
11002         
11003       default:
11004         EditGameEvent();
11005         break;
11006     }
11007
11008     gameMode = IcsIdle;
11009     ModeHighlight();
11010     return;
11011 }
11012
11013
11014 void
11015 EditGameEvent()
11016 {
11017     int i;
11018
11019     switch (gameMode) {
11020       case Training:
11021         SetTrainingModeOff();
11022         break;
11023       case MachinePlaysWhite:
11024       case MachinePlaysBlack:
11025       case BeginningOfGame:
11026         SendToProgram("force\n", &first);
11027         SetUserThinkingEnables();
11028         break;
11029       case PlayFromGameFile:
11030         (void) StopLoadGameTimer();
11031         if (gameFileFP != NULL) {
11032             gameFileFP = NULL;
11033         }
11034         break;
11035       case EditPosition:
11036         EditPositionDone(TRUE);
11037         break;
11038       case AnalyzeMode:
11039       case AnalyzeFile:
11040         ExitAnalyzeMode();
11041         SendToProgram("force\n", &first);
11042         break;
11043       case TwoMachinesPlay:
11044         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11045         ResurrectChessProgram();
11046         SetUserThinkingEnables();
11047         break;
11048       case EndOfGame:
11049         ResurrectChessProgram();
11050         break;
11051       case IcsPlayingBlack:
11052       case IcsPlayingWhite:
11053         DisplayError(_("Warning: You are still playing a game"), 0);
11054         break;
11055       case IcsObserving:
11056         DisplayError(_("Warning: You are still observing a game"), 0);
11057         break;
11058       case IcsExamining:
11059         DisplayError(_("Warning: You are still examining a game"), 0);
11060         break;
11061       case IcsIdle:
11062         break;
11063       case EditGame:
11064       default:
11065         return;
11066     }
11067     
11068     pausing = FALSE;
11069     StopClocks();
11070     first.offeredDraw = second.offeredDraw = 0;
11071
11072     if (gameMode == PlayFromGameFile) {
11073         whiteTimeRemaining = timeRemaining[0][currentMove];
11074         blackTimeRemaining = timeRemaining[1][currentMove];
11075         DisplayTitle("");
11076     }
11077
11078     if (gameMode == MachinePlaysWhite ||
11079         gameMode == MachinePlaysBlack ||
11080         gameMode == TwoMachinesPlay ||
11081         gameMode == EndOfGame) {
11082         i = forwardMostMove;
11083         while (i > currentMove) {
11084             SendToProgram("undo\n", &first);
11085             i--;
11086         }
11087         whiteTimeRemaining = timeRemaining[0][currentMove];
11088         blackTimeRemaining = timeRemaining[1][currentMove];
11089         DisplayBothClocks();
11090         if (whiteFlag || blackFlag) {
11091             whiteFlag = blackFlag = 0;
11092         }
11093         DisplayTitle("");
11094     }           
11095     
11096     gameMode = EditGame;
11097     ModeHighlight();
11098     SetGameInfo();
11099 }
11100
11101
11102 void
11103 EditPositionEvent()
11104 {
11105     if (gameMode == EditPosition) {
11106         EditGameEvent();
11107         return;
11108     }
11109     
11110     EditGameEvent();
11111     if (gameMode != EditGame) return;
11112     
11113     gameMode = EditPosition;
11114     ModeHighlight();
11115     SetGameInfo();
11116     if (currentMove > 0)
11117       CopyBoard(boards[0], boards[currentMove]);
11118     
11119     blackPlaysFirst = !WhiteOnMove(currentMove);
11120     ResetClocks();
11121     currentMove = forwardMostMove = backwardMostMove = 0;
11122     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11123     DisplayMove(-1);
11124 }
11125
11126 void
11127 ExitAnalyzeMode()
11128 {
11129     /* [DM] icsEngineAnalyze - possible call from other functions */
11130     if (appData.icsEngineAnalyze) {
11131         appData.icsEngineAnalyze = FALSE;
11132
11133         DisplayMessage("",_("Close ICS engine analyze..."));
11134     }
11135     if (first.analysisSupport && first.analyzing) {
11136       SendToProgram("exit\n", &first);
11137       first.analyzing = FALSE;
11138     }
11139     thinkOutput[0] = NULLCHAR;
11140 }
11141
11142 void
11143 EditPositionDone(Boolean fakeRights)
11144 {
11145     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11146
11147     startedFromSetupPosition = TRUE;
11148     InitChessProgram(&first, FALSE);
11149     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11150       boards[0][EP_STATUS] = EP_NONE;
11151       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11152     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11153         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11154         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11155       } else boards[0][CASTLING][2] = NoRights;
11156     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11157         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11158         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11159       } else boards[0][CASTLING][5] = NoRights;
11160     }
11161     SendToProgram("force\n", &first);
11162     if (blackPlaysFirst) {
11163         strcpy(moveList[0], "");
11164         strcpy(parseList[0], "");
11165         currentMove = forwardMostMove = backwardMostMove = 1;
11166         CopyBoard(boards[1], boards[0]);
11167     } else {
11168         currentMove = forwardMostMove = backwardMostMove = 0;
11169     }
11170     SendBoard(&first, forwardMostMove);
11171     if (appData.debugMode) {
11172         fprintf(debugFP, "EditPosDone\n");
11173     }
11174     DisplayTitle("");
11175     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11176     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11177     gameMode = EditGame;
11178     ModeHighlight();
11179     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11180     ClearHighlights(); /* [AS] */
11181 }
11182
11183 /* Pause for `ms' milliseconds */
11184 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11185 void
11186 TimeDelay(ms)
11187      long ms;
11188 {
11189     TimeMark m1, m2;
11190
11191     GetTimeMark(&m1);
11192     do {
11193         GetTimeMark(&m2);
11194     } while (SubtractTimeMarks(&m2, &m1) < ms);
11195 }
11196
11197 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11198 void
11199 SendMultiLineToICS(buf)
11200      char *buf;
11201 {
11202     char temp[MSG_SIZ+1], *p;
11203     int len;
11204
11205     len = strlen(buf);
11206     if (len > MSG_SIZ)
11207       len = MSG_SIZ;
11208   
11209     strncpy(temp, buf, len);
11210     temp[len] = 0;
11211
11212     p = temp;
11213     while (*p) {
11214         if (*p == '\n' || *p == '\r')
11215           *p = ' ';
11216         ++p;
11217     }
11218
11219     strcat(temp, "\n");
11220     SendToICS(temp);
11221     SendToPlayer(temp, strlen(temp));
11222 }
11223
11224 void
11225 SetWhiteToPlayEvent()
11226 {
11227     if (gameMode == EditPosition) {
11228         blackPlaysFirst = FALSE;
11229         DisplayBothClocks();    /* works because currentMove is 0 */
11230     } else if (gameMode == IcsExamining) {
11231         SendToICS(ics_prefix);
11232         SendToICS("tomove white\n");
11233     }
11234 }
11235
11236 void
11237 SetBlackToPlayEvent()
11238 {
11239     if (gameMode == EditPosition) {
11240         blackPlaysFirst = TRUE;
11241         currentMove = 1;        /* kludge */
11242         DisplayBothClocks();
11243         currentMove = 0;
11244     } else if (gameMode == IcsExamining) {
11245         SendToICS(ics_prefix);
11246         SendToICS("tomove black\n");
11247     }
11248 }
11249
11250 void
11251 EditPositionMenuEvent(selection, x, y)
11252      ChessSquare selection;
11253      int x, y;
11254 {
11255     char buf[MSG_SIZ];
11256     ChessSquare piece = boards[0][y][x];
11257
11258     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11259
11260     switch (selection) {
11261       case ClearBoard:
11262         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11263             SendToICS(ics_prefix);
11264             SendToICS("bsetup clear\n");
11265         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11266             SendToICS(ics_prefix);
11267             SendToICS("clearboard\n");
11268         } else {
11269             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11270                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11271                 for (y = 0; y < BOARD_HEIGHT; y++) {
11272                     if (gameMode == IcsExamining) {
11273                         if (boards[currentMove][y][x] != EmptySquare) {
11274                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11275                                     AAA + x, ONE + y);
11276                             SendToICS(buf);
11277                         }
11278                     } else {
11279                         boards[0][y][x] = p;
11280                     }
11281                 }
11282             }
11283         }
11284         if (gameMode == EditPosition) {
11285             DrawPosition(FALSE, boards[0]);
11286         }
11287         break;
11288
11289       case WhitePlay:
11290         SetWhiteToPlayEvent();
11291         break;
11292
11293       case BlackPlay:
11294         SetBlackToPlayEvent();
11295         break;
11296
11297       case EmptySquare:
11298         if (gameMode == IcsExamining) {
11299             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11300             SendToICS(buf);
11301         } else {
11302             boards[0][y][x] = EmptySquare;
11303             DrawPosition(FALSE, boards[0]);
11304         }
11305         break;
11306
11307       case PromotePiece:
11308         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11309            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11310             selection = (ChessSquare) (PROMOTED piece);
11311         } else if(piece == EmptySquare) selection = WhiteSilver;
11312         else selection = (ChessSquare)((int)piece - 1);
11313         goto defaultlabel;
11314
11315       case DemotePiece:
11316         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11317            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11318             selection = (ChessSquare) (DEMOTED piece);
11319         } else if(piece == EmptySquare) selection = BlackSilver;
11320         else selection = (ChessSquare)((int)piece + 1);       
11321         goto defaultlabel;
11322
11323       case WhiteQueen:
11324       case BlackQueen:
11325         if(gameInfo.variant == VariantShatranj ||
11326            gameInfo.variant == VariantXiangqi  ||
11327            gameInfo.variant == VariantCourier    )
11328             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11329         goto defaultlabel;
11330
11331       case WhiteKing:
11332       case BlackKing:
11333         if(gameInfo.variant == VariantXiangqi)
11334             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11335         if(gameInfo.variant == VariantKnightmate)
11336             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11337       default:
11338         defaultlabel:
11339         if (gameMode == IcsExamining) {
11340             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11341                     PieceToChar(selection), AAA + x, ONE + y);
11342             SendToICS(buf);
11343         } else {
11344             boards[0][y][x] = selection;
11345             DrawPosition(FALSE, boards[0]);
11346         }
11347         break;
11348     }
11349 }
11350
11351
11352 void
11353 DropMenuEvent(selection, x, y)
11354      ChessSquare selection;
11355      int x, y;
11356 {
11357     ChessMove moveType;
11358
11359     switch (gameMode) {
11360       case IcsPlayingWhite:
11361       case MachinePlaysBlack:
11362         if (!WhiteOnMove(currentMove)) {
11363             DisplayMoveError(_("It is Black's turn"));
11364             return;
11365         }
11366         moveType = WhiteDrop;
11367         break;
11368       case IcsPlayingBlack:
11369       case MachinePlaysWhite:
11370         if (WhiteOnMove(currentMove)) {
11371             DisplayMoveError(_("It is White's turn"));
11372             return;
11373         }
11374         moveType = BlackDrop;
11375         break;
11376       case EditGame:
11377         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11378         break;
11379       default:
11380         return;
11381     }
11382
11383     if (moveType == BlackDrop && selection < BlackPawn) {
11384       selection = (ChessSquare) ((int) selection
11385                                  + (int) BlackPawn - (int) WhitePawn);
11386     }
11387     if (boards[currentMove][y][x] != EmptySquare) {
11388         DisplayMoveError(_("That square is occupied"));
11389         return;
11390     }
11391
11392     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11393 }
11394
11395 void
11396 AcceptEvent()
11397 {
11398     /* Accept a pending offer of any kind from opponent */
11399     
11400     if (appData.icsActive) {
11401         SendToICS(ics_prefix);
11402         SendToICS("accept\n");
11403     } else if (cmailMsgLoaded) {
11404         if (currentMove == cmailOldMove &&
11405             commentList[cmailOldMove] != NULL &&
11406             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11407                    "Black offers a draw" : "White offers a draw")) {
11408             TruncateGame();
11409             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11410             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11411         } else {
11412             DisplayError(_("There is no pending offer on this move"), 0);
11413             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11414         }
11415     } else {
11416         /* Not used for offers from chess program */
11417     }
11418 }
11419
11420 void
11421 DeclineEvent()
11422 {
11423     /* Decline a pending offer of any kind from opponent */
11424     
11425     if (appData.icsActive) {
11426         SendToICS(ics_prefix);
11427         SendToICS("decline\n");
11428     } else if (cmailMsgLoaded) {
11429         if (currentMove == cmailOldMove &&
11430             commentList[cmailOldMove] != NULL &&
11431             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11432                    "Black offers a draw" : "White offers a draw")) {
11433 #ifdef NOTDEF
11434             AppendComment(cmailOldMove, "Draw declined", TRUE);
11435             DisplayComment(cmailOldMove - 1, "Draw declined");
11436 #endif /*NOTDEF*/
11437         } else {
11438             DisplayError(_("There is no pending offer on this move"), 0);
11439         }
11440     } else {
11441         /* Not used for offers from chess program */
11442     }
11443 }
11444
11445 void
11446 RematchEvent()
11447 {
11448     /* Issue ICS rematch command */
11449     if (appData.icsActive) {
11450         SendToICS(ics_prefix);
11451         SendToICS("rematch\n");
11452     }
11453 }
11454
11455 void
11456 CallFlagEvent()
11457 {
11458     /* Call your opponent's flag (claim a win on time) */
11459     if (appData.icsActive) {
11460         SendToICS(ics_prefix);
11461         SendToICS("flag\n");
11462     } else {
11463         switch (gameMode) {
11464           default:
11465             return;
11466           case MachinePlaysWhite:
11467             if (whiteFlag) {
11468                 if (blackFlag)
11469                   GameEnds(GameIsDrawn, "Both players ran out of time",
11470                            GE_PLAYER);
11471                 else
11472                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11473             } else {
11474                 DisplayError(_("Your opponent is not out of time"), 0);
11475             }
11476             break;
11477           case MachinePlaysBlack:
11478             if (blackFlag) {
11479                 if (whiteFlag)
11480                   GameEnds(GameIsDrawn, "Both players ran out of time",
11481                            GE_PLAYER);
11482                 else
11483                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11484             } else {
11485                 DisplayError(_("Your opponent is not out of time"), 0);
11486             }
11487             break;
11488         }
11489     }
11490 }
11491
11492 void
11493 DrawEvent()
11494 {
11495     /* Offer draw or accept pending draw offer from opponent */
11496     
11497     if (appData.icsActive) {
11498         /* Note: tournament rules require draw offers to be
11499            made after you make your move but before you punch
11500            your clock.  Currently ICS doesn't let you do that;
11501            instead, you immediately punch your clock after making
11502            a move, but you can offer a draw at any time. */
11503         
11504         SendToICS(ics_prefix);
11505         SendToICS("draw\n");
11506     } else if (cmailMsgLoaded) {
11507         if (currentMove == cmailOldMove &&
11508             commentList[cmailOldMove] != NULL &&
11509             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11510                    "Black offers a draw" : "White offers a draw")) {
11511             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11512             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11513         } else if (currentMove == cmailOldMove + 1) {
11514             char *offer = WhiteOnMove(cmailOldMove) ?
11515               "White offers a draw" : "Black offers a draw";
11516             AppendComment(currentMove, offer, TRUE);
11517             DisplayComment(currentMove - 1, offer);
11518             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11519         } else {
11520             DisplayError(_("You must make your move before offering a draw"), 0);
11521             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11522         }
11523     } else if (first.offeredDraw) {
11524         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11525     } else {
11526         if (first.sendDrawOffers) {
11527             SendToProgram("draw\n", &first);
11528             userOfferedDraw = TRUE;
11529         }
11530     }
11531 }
11532
11533 void
11534 AdjournEvent()
11535 {
11536     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11537     
11538     if (appData.icsActive) {
11539         SendToICS(ics_prefix);
11540         SendToICS("adjourn\n");
11541     } else {
11542         /* Currently GNU Chess doesn't offer or accept Adjourns */
11543     }
11544 }
11545
11546
11547 void
11548 AbortEvent()
11549 {
11550     /* Offer Abort or accept pending Abort offer from opponent */
11551     
11552     if (appData.icsActive) {
11553         SendToICS(ics_prefix);
11554         SendToICS("abort\n");
11555     } else {
11556         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11557     }
11558 }
11559
11560 void
11561 ResignEvent()
11562 {
11563     /* Resign.  You can do this even if it's not your turn. */
11564     
11565     if (appData.icsActive) {
11566         SendToICS(ics_prefix);
11567         SendToICS("resign\n");
11568     } else {
11569         switch (gameMode) {
11570           case MachinePlaysWhite:
11571             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11572             break;
11573           case MachinePlaysBlack:
11574             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11575             break;
11576           case EditGame:
11577             if (cmailMsgLoaded) {
11578                 TruncateGame();
11579                 if (WhiteOnMove(cmailOldMove)) {
11580                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11581                 } else {
11582                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11583                 }
11584                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11585             }
11586             break;
11587           default:
11588             break;
11589         }
11590     }
11591 }
11592
11593
11594 void
11595 StopObservingEvent()
11596 {
11597     /* Stop observing current games */
11598     SendToICS(ics_prefix);
11599     SendToICS("unobserve\n");
11600 }
11601
11602 void
11603 StopExaminingEvent()
11604 {
11605     /* Stop observing current game */
11606     SendToICS(ics_prefix);
11607     SendToICS("unexamine\n");
11608 }
11609
11610 void
11611 ForwardInner(target)
11612      int target;
11613 {
11614     int limit;
11615
11616     if (appData.debugMode)
11617         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11618                 target, currentMove, forwardMostMove);
11619
11620     if (gameMode == EditPosition)
11621       return;
11622
11623     if (gameMode == PlayFromGameFile && !pausing)
11624       PauseEvent();
11625     
11626     if (gameMode == IcsExamining && pausing)
11627       limit = pauseExamForwardMostMove;
11628     else
11629       limit = forwardMostMove;
11630     
11631     if (target > limit) target = limit;
11632
11633     if (target > 0 && moveList[target - 1][0]) {
11634         int fromX, fromY, toX, toY;
11635         toX = moveList[target - 1][2] - AAA;
11636         toY = moveList[target - 1][3] - ONE;
11637         if (moveList[target - 1][1] == '@') {
11638             if (appData.highlightLastMove) {
11639                 SetHighlights(-1, -1, toX, toY);
11640             }
11641         } else {
11642             fromX = moveList[target - 1][0] - AAA;
11643             fromY = moveList[target - 1][1] - ONE;
11644             if (target == currentMove + 1) {
11645                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11646             }
11647             if (appData.highlightLastMove) {
11648                 SetHighlights(fromX, fromY, toX, toY);
11649             }
11650         }
11651     }
11652     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11653         gameMode == Training || gameMode == PlayFromGameFile || 
11654         gameMode == AnalyzeFile) {
11655         while (currentMove < target) {
11656             SendMoveToProgram(currentMove++, &first);
11657         }
11658     } else {
11659         currentMove = target;
11660     }
11661     
11662     if (gameMode == EditGame || gameMode == EndOfGame) {
11663         whiteTimeRemaining = timeRemaining[0][currentMove];
11664         blackTimeRemaining = timeRemaining[1][currentMove];
11665     }
11666     DisplayBothClocks();
11667     DisplayMove(currentMove - 1);
11668     DrawPosition(FALSE, boards[currentMove]);
11669     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11670     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11671         DisplayComment(currentMove - 1, commentList[currentMove]);
11672     }
11673 }
11674
11675
11676 void
11677 ForwardEvent()
11678 {
11679     if (gameMode == IcsExamining && !pausing) {
11680         SendToICS(ics_prefix);
11681         SendToICS("forward\n");
11682     } else {
11683         ForwardInner(currentMove + 1);
11684     }
11685 }
11686
11687 void
11688 ToEndEvent()
11689 {
11690     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11691         /* to optimze, we temporarily turn off analysis mode while we feed
11692          * the remaining moves to the engine. Otherwise we get analysis output
11693          * after each move.
11694          */ 
11695         if (first.analysisSupport) {
11696           SendToProgram("exit\nforce\n", &first);
11697           first.analyzing = FALSE;
11698         }
11699     }
11700         
11701     if (gameMode == IcsExamining && !pausing) {
11702         SendToICS(ics_prefix);
11703         SendToICS("forward 999999\n");
11704     } else {
11705         ForwardInner(forwardMostMove);
11706     }
11707
11708     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11709         /* we have fed all the moves, so reactivate analysis mode */
11710         SendToProgram("analyze\n", &first);
11711         first.analyzing = TRUE;
11712         /*first.maybeThinking = TRUE;*/
11713         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11714     }
11715 }
11716
11717 void
11718 BackwardInner(target)
11719      int target;
11720 {
11721     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11722
11723     if (appData.debugMode)
11724         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11725                 target, currentMove, forwardMostMove);
11726
11727     if (gameMode == EditPosition) return;
11728     if (currentMove <= backwardMostMove) {
11729         ClearHighlights();
11730         DrawPosition(full_redraw, boards[currentMove]);
11731         return;
11732     }
11733     if (gameMode == PlayFromGameFile && !pausing)
11734       PauseEvent();
11735     
11736     if (moveList[target][0]) {
11737         int fromX, fromY, toX, toY;
11738         toX = moveList[target][2] - AAA;
11739         toY = moveList[target][3] - ONE;
11740         if (moveList[target][1] == '@') {
11741             if (appData.highlightLastMove) {
11742                 SetHighlights(-1, -1, toX, toY);
11743             }
11744         } else {
11745             fromX = moveList[target][0] - AAA;
11746             fromY = moveList[target][1] - ONE;
11747             if (target == currentMove - 1) {
11748                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11749             }
11750             if (appData.highlightLastMove) {
11751                 SetHighlights(fromX, fromY, toX, toY);
11752             }
11753         }
11754     }
11755     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11756         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11757         while (currentMove > target) {
11758             SendToProgram("undo\n", &first);
11759             currentMove--;
11760         }
11761     } else {
11762         currentMove = target;
11763     }
11764     
11765     if (gameMode == EditGame || gameMode == EndOfGame) {
11766         whiteTimeRemaining = timeRemaining[0][currentMove];
11767         blackTimeRemaining = timeRemaining[1][currentMove];
11768     }
11769     DisplayBothClocks();
11770     DisplayMove(currentMove - 1);
11771     DrawPosition(full_redraw, boards[currentMove]);
11772     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11773     // [HGM] PV info: routine tests if comment empty
11774     DisplayComment(currentMove - 1, commentList[currentMove]);
11775 }
11776
11777 void
11778 BackwardEvent()
11779 {
11780     if (gameMode == IcsExamining && !pausing) {
11781         SendToICS(ics_prefix);
11782         SendToICS("backward\n");
11783     } else {
11784         BackwardInner(currentMove - 1);
11785     }
11786 }
11787
11788 void
11789 ToStartEvent()
11790 {
11791     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11792         /* to optimize, we temporarily turn off analysis mode while we undo
11793          * all the moves. Otherwise we get analysis output after each undo.
11794          */ 
11795         if (first.analysisSupport) {
11796           SendToProgram("exit\nforce\n", &first);
11797           first.analyzing = FALSE;
11798         }
11799     }
11800
11801     if (gameMode == IcsExamining && !pausing) {
11802         SendToICS(ics_prefix);
11803         SendToICS("backward 999999\n");
11804     } else {
11805         BackwardInner(backwardMostMove);
11806     }
11807
11808     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11809         /* we have fed all the moves, so reactivate analysis mode */
11810         SendToProgram("analyze\n", &first);
11811         first.analyzing = TRUE;
11812         /*first.maybeThinking = TRUE;*/
11813         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11814     }
11815 }
11816
11817 void
11818 ToNrEvent(int to)
11819 {
11820   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11821   if (to >= forwardMostMove) to = forwardMostMove;
11822   if (to <= backwardMostMove) to = backwardMostMove;
11823   if (to < currentMove) {
11824     BackwardInner(to);
11825   } else {
11826     ForwardInner(to);
11827   }
11828 }
11829
11830 void
11831 RevertEvent()
11832 {
11833     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11834         return;
11835     }
11836     if (gameMode != IcsExamining) {
11837         DisplayError(_("You are not examining a game"), 0);
11838         return;
11839     }
11840     if (pausing) {
11841         DisplayError(_("You can't revert while pausing"), 0);
11842         return;
11843     }
11844     SendToICS(ics_prefix);
11845     SendToICS("revert\n");
11846 }
11847
11848 void
11849 RetractMoveEvent()
11850 {
11851     switch (gameMode) {
11852       case MachinePlaysWhite:
11853       case MachinePlaysBlack:
11854         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11855             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11856             return;
11857         }
11858         if (forwardMostMove < 2) return;
11859         currentMove = forwardMostMove = forwardMostMove - 2;
11860         whiteTimeRemaining = timeRemaining[0][currentMove];
11861         blackTimeRemaining = timeRemaining[1][currentMove];
11862         DisplayBothClocks();
11863         DisplayMove(currentMove - 1);
11864         ClearHighlights();/*!! could figure this out*/
11865         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11866         SendToProgram("remove\n", &first);
11867         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11868         break;
11869
11870       case BeginningOfGame:
11871       default:
11872         break;
11873
11874       case IcsPlayingWhite:
11875       case IcsPlayingBlack:
11876         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11877             SendToICS(ics_prefix);
11878             SendToICS("takeback 2\n");
11879         } else {
11880             SendToICS(ics_prefix);
11881             SendToICS("takeback 1\n");
11882         }
11883         break;
11884     }
11885 }
11886
11887 void
11888 MoveNowEvent()
11889 {
11890     ChessProgramState *cps;
11891
11892     switch (gameMode) {
11893       case MachinePlaysWhite:
11894         if (!WhiteOnMove(forwardMostMove)) {
11895             DisplayError(_("It is your turn"), 0);
11896             return;
11897         }
11898         cps = &first;
11899         break;
11900       case MachinePlaysBlack:
11901         if (WhiteOnMove(forwardMostMove)) {
11902             DisplayError(_("It is your turn"), 0);
11903             return;
11904         }
11905         cps = &first;
11906         break;
11907       case TwoMachinesPlay:
11908         if (WhiteOnMove(forwardMostMove) ==
11909             (first.twoMachinesColor[0] == 'w')) {
11910             cps = &first;
11911         } else {
11912             cps = &second;
11913         }
11914         break;
11915       case BeginningOfGame:
11916       default:
11917         return;
11918     }
11919     SendToProgram("?\n", cps);
11920 }
11921
11922 void
11923 TruncateGameEvent()
11924 {
11925     EditGameEvent();
11926     if (gameMode != EditGame) return;
11927     TruncateGame();
11928 }
11929
11930 void
11931 TruncateGame()
11932 {
11933     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
11934     if (forwardMostMove > currentMove) {
11935         if (gameInfo.resultDetails != NULL) {
11936             free(gameInfo.resultDetails);
11937             gameInfo.resultDetails = NULL;
11938             gameInfo.result = GameUnfinished;
11939         }
11940         forwardMostMove = currentMove;
11941         HistorySet(parseList, backwardMostMove, forwardMostMove,
11942                    currentMove-1);
11943     }
11944 }
11945
11946 void
11947 HintEvent()
11948 {
11949     if (appData.noChessProgram) return;
11950     switch (gameMode) {
11951       case MachinePlaysWhite:
11952         if (WhiteOnMove(forwardMostMove)) {
11953             DisplayError(_("Wait until your turn"), 0);
11954             return;
11955         }
11956         break;
11957       case BeginningOfGame:
11958       case MachinePlaysBlack:
11959         if (!WhiteOnMove(forwardMostMove)) {
11960             DisplayError(_("Wait until your turn"), 0);
11961             return;
11962         }
11963         break;
11964       default:
11965         DisplayError(_("No hint available"), 0);
11966         return;
11967     }
11968     SendToProgram("hint\n", &first);
11969     hintRequested = TRUE;
11970 }
11971
11972 void
11973 BookEvent()
11974 {
11975     if (appData.noChessProgram) return;
11976     switch (gameMode) {
11977       case MachinePlaysWhite:
11978         if (WhiteOnMove(forwardMostMove)) {
11979             DisplayError(_("Wait until your turn"), 0);
11980             return;
11981         }
11982         break;
11983       case BeginningOfGame:
11984       case MachinePlaysBlack:
11985         if (!WhiteOnMove(forwardMostMove)) {
11986             DisplayError(_("Wait until your turn"), 0);
11987             return;
11988         }
11989         break;
11990       case EditPosition:
11991         EditPositionDone(TRUE);
11992         break;
11993       case TwoMachinesPlay:
11994         return;
11995       default:
11996         break;
11997     }
11998     SendToProgram("bk\n", &first);
11999     bookOutput[0] = NULLCHAR;
12000     bookRequested = TRUE;
12001 }
12002
12003 void
12004 AboutGameEvent()
12005 {
12006     char *tags = PGNTags(&gameInfo);
12007     TagsPopUp(tags, CmailMsg());
12008     free(tags);
12009 }
12010
12011 /* end button procedures */
12012
12013 void
12014 PrintPosition(fp, move)
12015      FILE *fp;
12016      int move;
12017 {
12018     int i, j;
12019     
12020     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12021         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12022             char c = PieceToChar(boards[move][i][j]);
12023             fputc(c == 'x' ? '.' : c, fp);
12024             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12025         }
12026     }
12027     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12028       fprintf(fp, "white to play\n");
12029     else
12030       fprintf(fp, "black to play\n");
12031 }
12032
12033 void
12034 PrintOpponents(fp)
12035      FILE *fp;
12036 {
12037     if (gameInfo.white != NULL) {
12038         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12039     } else {
12040         fprintf(fp, "\n");
12041     }
12042 }
12043
12044 /* Find last component of program's own name, using some heuristics */
12045 void
12046 TidyProgramName(prog, host, buf)
12047      char *prog, *host, buf[MSG_SIZ];
12048 {
12049     char *p, *q;
12050     int local = (strcmp(host, "localhost") == 0);
12051     while (!local && (p = strchr(prog, ';')) != NULL) {
12052         p++;
12053         while (*p == ' ') p++;
12054         prog = p;
12055     }
12056     if (*prog == '"' || *prog == '\'') {
12057         q = strchr(prog + 1, *prog);
12058     } else {
12059         q = strchr(prog, ' ');
12060     }
12061     if (q == NULL) q = prog + strlen(prog);
12062     p = q;
12063     while (p >= prog && *p != '/' && *p != '\\') p--;
12064     p++;
12065     if(p == prog && *p == '"') p++;
12066     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12067     memcpy(buf, p, q - p);
12068     buf[q - p] = NULLCHAR;
12069     if (!local) {
12070         strcat(buf, "@");
12071         strcat(buf, host);
12072     }
12073 }
12074
12075 char *
12076 TimeControlTagValue()
12077 {
12078     char buf[MSG_SIZ];
12079     if (!appData.clockMode) {
12080         strcpy(buf, "-");
12081     } else if (movesPerSession > 0) {
12082         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12083     } else if (timeIncrement == 0) {
12084         sprintf(buf, "%ld", timeControl/1000);
12085     } else {
12086         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12087     }
12088     return StrSave(buf);
12089 }
12090
12091 void
12092 SetGameInfo()
12093 {
12094     /* This routine is used only for certain modes */
12095     VariantClass v = gameInfo.variant;
12096     ChessMove r = GameUnfinished;
12097     char *p = NULL;
12098
12099     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12100         r = gameInfo.result; 
12101         p = gameInfo.resultDetails; 
12102         gameInfo.resultDetails = NULL;
12103     }
12104     ClearGameInfo(&gameInfo);
12105     gameInfo.variant = v;
12106
12107     switch (gameMode) {
12108       case MachinePlaysWhite:
12109         gameInfo.event = StrSave( appData.pgnEventHeader );
12110         gameInfo.site = StrSave(HostName());
12111         gameInfo.date = PGNDate();
12112         gameInfo.round = StrSave("-");
12113         gameInfo.white = StrSave(first.tidy);
12114         gameInfo.black = StrSave(UserName());
12115         gameInfo.timeControl = TimeControlTagValue();
12116         break;
12117
12118       case MachinePlaysBlack:
12119         gameInfo.event = StrSave( appData.pgnEventHeader );
12120         gameInfo.site = StrSave(HostName());
12121         gameInfo.date = PGNDate();
12122         gameInfo.round = StrSave("-");
12123         gameInfo.white = StrSave(UserName());
12124         gameInfo.black = StrSave(first.tidy);
12125         gameInfo.timeControl = TimeControlTagValue();
12126         break;
12127
12128       case TwoMachinesPlay:
12129         gameInfo.event = StrSave( appData.pgnEventHeader );
12130         gameInfo.site = StrSave(HostName());
12131         gameInfo.date = PGNDate();
12132         if (matchGame > 0) {
12133             char buf[MSG_SIZ];
12134             sprintf(buf, "%d", matchGame);
12135             gameInfo.round = StrSave(buf);
12136         } else {
12137             gameInfo.round = StrSave("-");
12138         }
12139         if (first.twoMachinesColor[0] == 'w') {
12140             gameInfo.white = StrSave(first.tidy);
12141             gameInfo.black = StrSave(second.tidy);
12142         } else {
12143             gameInfo.white = StrSave(second.tidy);
12144             gameInfo.black = StrSave(first.tidy);
12145         }
12146         gameInfo.timeControl = TimeControlTagValue();
12147         break;
12148
12149       case EditGame:
12150         gameInfo.event = StrSave("Edited game");
12151         gameInfo.site = StrSave(HostName());
12152         gameInfo.date = PGNDate();
12153         gameInfo.round = StrSave("-");
12154         gameInfo.white = StrSave("-");
12155         gameInfo.black = StrSave("-");
12156         gameInfo.result = r;
12157         gameInfo.resultDetails = p;
12158         break;
12159
12160       case EditPosition:
12161         gameInfo.event = StrSave("Edited position");
12162         gameInfo.site = StrSave(HostName());
12163         gameInfo.date = PGNDate();
12164         gameInfo.round = StrSave("-");
12165         gameInfo.white = StrSave("-");
12166         gameInfo.black = StrSave("-");
12167         break;
12168
12169       case IcsPlayingWhite:
12170       case IcsPlayingBlack:
12171       case IcsObserving:
12172       case IcsExamining:
12173         break;
12174
12175       case PlayFromGameFile:
12176         gameInfo.event = StrSave("Game from non-PGN file");
12177         gameInfo.site = StrSave(HostName());
12178         gameInfo.date = PGNDate();
12179         gameInfo.round = StrSave("-");
12180         gameInfo.white = StrSave("?");
12181         gameInfo.black = StrSave("?");
12182         break;
12183
12184       default:
12185         break;
12186     }
12187 }
12188
12189 void
12190 ReplaceComment(index, text)
12191      int index;
12192      char *text;
12193 {
12194     int len;
12195
12196     while (*text == '\n') text++;
12197     len = strlen(text);
12198     while (len > 0 && text[len - 1] == '\n') len--;
12199
12200     if (commentList[index] != NULL)
12201       free(commentList[index]);
12202
12203     if (len == 0) {
12204         commentList[index] = NULL;
12205         return;
12206     }
12207   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12208       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12209       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12210     commentList[index] = (char *) malloc(len + 2);
12211     strncpy(commentList[index], text, len);
12212     commentList[index][len] = '\n';
12213     commentList[index][len + 1] = NULLCHAR;
12214   } else { 
12215     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12216     char *p;
12217     commentList[index] = (char *) malloc(len + 6);
12218     strcpy(commentList[index], "{\n");
12219     strncpy(commentList[index]+2, text, len);
12220     commentList[index][len+2] = NULLCHAR;
12221     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12222     strcat(commentList[index], "\n}\n");
12223   }
12224 }
12225
12226 void
12227 CrushCRs(text)
12228      char *text;
12229 {
12230   char *p = text;
12231   char *q = text;
12232   char ch;
12233
12234   do {
12235     ch = *p++;
12236     if (ch == '\r') continue;
12237     *q++ = ch;
12238   } while (ch != '\0');
12239 }
12240
12241 void
12242 AppendComment(index, text, addBraces)
12243      int index;
12244      char *text;
12245      Boolean addBraces; // [HGM] braces: tells if we should add {}
12246 {
12247     int oldlen, len;
12248     char *old;
12249
12250 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12251     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12252
12253     CrushCRs(text);
12254     while (*text == '\n') text++;
12255     len = strlen(text);
12256     while (len > 0 && text[len - 1] == '\n') len--;
12257
12258     if (len == 0) return;
12259
12260     if (commentList[index] != NULL) {
12261         old = commentList[index];
12262         oldlen = strlen(old);
12263         while(commentList[index][oldlen-1] ==  '\n')
12264           commentList[index][--oldlen] = NULLCHAR;
12265         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12266         strcpy(commentList[index], old);
12267         free(old);
12268         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12269         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12270           if(addBraces) addBraces = FALSE; else { text++; len--; }
12271           while (*text == '\n') { text++; len--; }
12272           commentList[index][--oldlen] = NULLCHAR;
12273       }
12274         if(addBraces) strcat(commentList[index], "\n{\n");
12275         else          strcat(commentList[index], "\n");
12276         strcat(commentList[index], text);
12277         if(addBraces) strcat(commentList[index], "\n}\n");
12278         else          strcat(commentList[index], "\n");
12279     } else {
12280         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12281         if(addBraces)
12282              strcpy(commentList[index], "{\n");
12283         else commentList[index][0] = NULLCHAR;
12284         strcat(commentList[index], text);
12285         strcat(commentList[index], "\n");
12286         if(addBraces) strcat(commentList[index], "}\n");
12287     }
12288 }
12289
12290 static char * FindStr( char * text, char * sub_text )
12291 {
12292     char * result = strstr( text, sub_text );
12293
12294     if( result != NULL ) {
12295         result += strlen( sub_text );
12296     }
12297
12298     return result;
12299 }
12300
12301 /* [AS] Try to extract PV info from PGN comment */
12302 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12303 char *GetInfoFromComment( int index, char * text )
12304 {
12305     char * sep = text;
12306
12307     if( text != NULL && index > 0 ) {
12308         int score = 0;
12309         int depth = 0;
12310         int time = -1, sec = 0, deci;
12311         char * s_eval = FindStr( text, "[%eval " );
12312         char * s_emt = FindStr( text, "[%emt " );
12313
12314         if( s_eval != NULL || s_emt != NULL ) {
12315             /* New style */
12316             char delim;
12317
12318             if( s_eval != NULL ) {
12319                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12320                     return text;
12321                 }
12322
12323                 if( delim != ']' ) {
12324                     return text;
12325                 }
12326             }
12327
12328             if( s_emt != NULL ) {
12329             }
12330                 return text;
12331         }
12332         else {
12333             /* We expect something like: [+|-]nnn.nn/dd */
12334             int score_lo = 0;
12335
12336             if(*text != '{') return text; // [HGM] braces: must be normal comment
12337
12338             sep = strchr( text, '/' );
12339             if( sep == NULL || sep < (text+4) ) {
12340                 return text;
12341             }
12342
12343             time = -1; sec = -1; deci = -1;
12344             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12345                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12346                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12347                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12348                 return text;
12349             }
12350
12351             if( score_lo < 0 || score_lo >= 100 ) {
12352                 return text;
12353             }
12354
12355             if(sec >= 0) time = 600*time + 10*sec; else
12356             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12357
12358             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12359
12360             /* [HGM] PV time: now locate end of PV info */
12361             while( *++sep >= '0' && *sep <= '9'); // strip depth
12362             if(time >= 0)
12363             while( *++sep >= '0' && *sep <= '9'); // strip time
12364             if(sec >= 0)
12365             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12366             if(deci >= 0)
12367             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12368             while(*sep == ' ') sep++;
12369         }
12370
12371         if( depth <= 0 ) {
12372             return text;
12373         }
12374
12375         if( time < 0 ) {
12376             time = -1;
12377         }
12378
12379         pvInfoList[index-1].depth = depth;
12380         pvInfoList[index-1].score = score;
12381         pvInfoList[index-1].time  = 10*time; // centi-sec
12382         if(*sep == '}') *sep = 0; else *--sep = '{';
12383     }
12384     return sep;
12385 }
12386
12387 void
12388 SendToProgram(message, cps)
12389      char *message;
12390      ChessProgramState *cps;
12391 {
12392     int count, outCount, error;
12393     char buf[MSG_SIZ];
12394
12395     if (cps->pr == NULL) return;
12396     Attention(cps);
12397     
12398     if (appData.debugMode) {
12399         TimeMark now;
12400         GetTimeMark(&now);
12401         fprintf(debugFP, "%ld >%-6s: %s", 
12402                 SubtractTimeMarks(&now, &programStartTime),
12403                 cps->which, message);
12404     }
12405     
12406     count = strlen(message);
12407     outCount = OutputToProcess(cps->pr, message, count, &error);
12408     if (outCount < count && !exiting 
12409                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12410         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12411         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12412             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12413                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12414                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12415             } else {
12416                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12417             }
12418             gameInfo.resultDetails = StrSave(buf);
12419         }
12420         DisplayFatalError(buf, error, 1);
12421     }
12422 }
12423
12424 void
12425 ReceiveFromProgram(isr, closure, message, count, error)
12426      InputSourceRef isr;
12427      VOIDSTAR closure;
12428      char *message;
12429      int count;
12430      int error;
12431 {
12432     char *end_str;
12433     char buf[MSG_SIZ];
12434     ChessProgramState *cps = (ChessProgramState *)closure;
12435
12436     if (isr != cps->isr) return; /* Killed intentionally */
12437     if (count <= 0) {
12438         if (count == 0) {
12439             sprintf(buf,
12440                     _("Error: %s chess program (%s) exited unexpectedly"),
12441                     cps->which, cps->program);
12442         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12443                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12444                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12445                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12446                 } else {
12447                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12448                 }
12449                 gameInfo.resultDetails = StrSave(buf);
12450             }
12451             RemoveInputSource(cps->isr);
12452             DisplayFatalError(buf, 0, 1);
12453         } else {
12454             sprintf(buf,
12455                     _("Error reading from %s chess program (%s)"),
12456                     cps->which, cps->program);
12457             RemoveInputSource(cps->isr);
12458
12459             /* [AS] Program is misbehaving badly... kill it */
12460             if( count == -2 ) {
12461                 DestroyChildProcess( cps->pr, 9 );
12462                 cps->pr = NoProc;
12463             }
12464
12465             DisplayFatalError(buf, error, 1);
12466         }
12467         return;
12468     }
12469     
12470     if ((end_str = strchr(message, '\r')) != NULL)
12471       *end_str = NULLCHAR;
12472     if ((end_str = strchr(message, '\n')) != NULL)
12473       *end_str = NULLCHAR;
12474     
12475     if (appData.debugMode) {
12476         TimeMark now; int print = 1;
12477         char *quote = ""; char c; int i;
12478
12479         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12480                 char start = message[0];
12481                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12482                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12483                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12484                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12485                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12486                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12487                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12488                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12489                         { quote = "# "; print = (appData.engineComments == 2); }
12490                 message[0] = start; // restore original message
12491         }
12492         if(print) {
12493                 GetTimeMark(&now);
12494                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12495                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12496                         quote,
12497                         message);
12498         }
12499     }
12500
12501     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12502     if (appData.icsEngineAnalyze) {
12503         if (strstr(message, "whisper") != NULL ||
12504              strstr(message, "kibitz") != NULL || 
12505             strstr(message, "tellics") != NULL) return;
12506     }
12507
12508     HandleMachineMove(message, cps);
12509 }
12510
12511
12512 void
12513 SendTimeControl(cps, mps, tc, inc, sd, st)
12514      ChessProgramState *cps;
12515      int mps, inc, sd, st;
12516      long tc;
12517 {
12518     char buf[MSG_SIZ];
12519     int seconds;
12520
12521     if( timeControl_2 > 0 ) {
12522         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12523             tc = timeControl_2;
12524         }
12525     }
12526     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12527     inc /= cps->timeOdds;
12528     st  /= cps->timeOdds;
12529
12530     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12531
12532     if (st > 0) {
12533       /* Set exact time per move, normally using st command */
12534       if (cps->stKludge) {
12535         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12536         seconds = st % 60;
12537         if (seconds == 0) {
12538           sprintf(buf, "level 1 %d\n", st/60);
12539         } else {
12540           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12541         }
12542       } else {
12543         sprintf(buf, "st %d\n", st);
12544       }
12545     } else {
12546       /* Set conventional or incremental time control, using level command */
12547       if (seconds == 0) {
12548         /* Note old gnuchess bug -- minutes:seconds used to not work.
12549            Fixed in later versions, but still avoid :seconds
12550            when seconds is 0. */
12551         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12552       } else {
12553         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12554                 seconds, inc/1000);
12555       }
12556     }
12557     SendToProgram(buf, cps);
12558
12559     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12560     /* Orthogonally, limit search to given depth */
12561     if (sd > 0) {
12562       if (cps->sdKludge) {
12563         sprintf(buf, "depth\n%d\n", sd);
12564       } else {
12565         sprintf(buf, "sd %d\n", sd);
12566       }
12567       SendToProgram(buf, cps);
12568     }
12569
12570     if(cps->nps > 0) { /* [HGM] nps */
12571         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12572         else {
12573                 sprintf(buf, "nps %d\n", cps->nps);
12574               SendToProgram(buf, cps);
12575         }
12576     }
12577 }
12578
12579 ChessProgramState *WhitePlayer()
12580 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12581 {
12582     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12583        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12584         return &second;
12585     return &first;
12586 }
12587
12588 void
12589 SendTimeRemaining(cps, machineWhite)
12590      ChessProgramState *cps;
12591      int /*boolean*/ machineWhite;
12592 {
12593     char message[MSG_SIZ];
12594     long time, otime;
12595
12596     /* Note: this routine must be called when the clocks are stopped
12597        or when they have *just* been set or switched; otherwise
12598        it will be off by the time since the current tick started.
12599     */
12600     if (machineWhite) {
12601         time = whiteTimeRemaining / 10;
12602         otime = blackTimeRemaining / 10;
12603     } else {
12604         time = blackTimeRemaining / 10;
12605         otime = whiteTimeRemaining / 10;
12606     }
12607     /* [HGM] translate opponent's time by time-odds factor */
12608     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12609     if (appData.debugMode) {
12610         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12611     }
12612
12613     if (time <= 0) time = 1;
12614     if (otime <= 0) otime = 1;
12615     
12616     sprintf(message, "time %ld\n", time);
12617     SendToProgram(message, cps);
12618
12619     sprintf(message, "otim %ld\n", otime);
12620     SendToProgram(message, cps);
12621 }
12622
12623 int
12624 BoolFeature(p, name, loc, cps)
12625      char **p;
12626      char *name;
12627      int *loc;
12628      ChessProgramState *cps;
12629 {
12630   char buf[MSG_SIZ];
12631   int len = strlen(name);
12632   int val;
12633   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12634     (*p) += len + 1;
12635     sscanf(*p, "%d", &val);
12636     *loc = (val != 0);
12637     while (**p && **p != ' ') (*p)++;
12638     sprintf(buf, "accepted %s\n", name);
12639     SendToProgram(buf, cps);
12640     return TRUE;
12641   }
12642   return FALSE;
12643 }
12644
12645 int
12646 IntFeature(p, name, loc, cps)
12647      char **p;
12648      char *name;
12649      int *loc;
12650      ChessProgramState *cps;
12651 {
12652   char buf[MSG_SIZ];
12653   int len = strlen(name);
12654   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12655     (*p) += len + 1;
12656     sscanf(*p, "%d", loc);
12657     while (**p && **p != ' ') (*p)++;
12658     sprintf(buf, "accepted %s\n", name);
12659     SendToProgram(buf, cps);
12660     return TRUE;
12661   }
12662   return FALSE;
12663 }
12664
12665 int
12666 StringFeature(p, name, loc, cps)
12667      char **p;
12668      char *name;
12669      char loc[];
12670      ChessProgramState *cps;
12671 {
12672   char buf[MSG_SIZ];
12673   int len = strlen(name);
12674   if (strncmp((*p), name, len) == 0
12675       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12676     (*p) += len + 2;
12677     sscanf(*p, "%[^\"]", loc);
12678     while (**p && **p != '\"') (*p)++;
12679     if (**p == '\"') (*p)++;
12680     sprintf(buf, "accepted %s\n", name);
12681     SendToProgram(buf, cps);
12682     return TRUE;
12683   }
12684   return FALSE;
12685 }
12686
12687 int 
12688 ParseOption(Option *opt, ChessProgramState *cps)
12689 // [HGM] options: process the string that defines an engine option, and determine
12690 // name, type, default value, and allowed value range
12691 {
12692         char *p, *q, buf[MSG_SIZ];
12693         int n, min = (-1)<<31, max = 1<<31, def;
12694
12695         if(p = strstr(opt->name, " -spin ")) {
12696             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12697             if(max < min) max = min; // enforce consistency
12698             if(def < min) def = min;
12699             if(def > max) def = max;
12700             opt->value = def;
12701             opt->min = min;
12702             opt->max = max;
12703             opt->type = Spin;
12704         } else if((p = strstr(opt->name, " -slider "))) {
12705             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12706             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12707             if(max < min) max = min; // enforce consistency
12708             if(def < min) def = min;
12709             if(def > max) def = max;
12710             opt->value = def;
12711             opt->min = min;
12712             opt->max = max;
12713             opt->type = Spin; // Slider;
12714         } else if((p = strstr(opt->name, " -string "))) {
12715             opt->textValue = p+9;
12716             opt->type = TextBox;
12717         } else if((p = strstr(opt->name, " -file "))) {
12718             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12719             opt->textValue = p+7;
12720             opt->type = TextBox; // FileName;
12721         } else if((p = strstr(opt->name, " -path "))) {
12722             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12723             opt->textValue = p+7;
12724             opt->type = TextBox; // PathName;
12725         } else if(p = strstr(opt->name, " -check ")) {
12726             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12727             opt->value = (def != 0);
12728             opt->type = CheckBox;
12729         } else if(p = strstr(opt->name, " -combo ")) {
12730             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12731             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12732             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12733             opt->value = n = 0;
12734             while(q = StrStr(q, " /// ")) {
12735                 n++; *q = 0;    // count choices, and null-terminate each of them
12736                 q += 5;
12737                 if(*q == '*') { // remember default, which is marked with * prefix
12738                     q++;
12739                     opt->value = n;
12740                 }
12741                 cps->comboList[cps->comboCnt++] = q;
12742             }
12743             cps->comboList[cps->comboCnt++] = NULL;
12744             opt->max = n + 1;
12745             opt->type = ComboBox;
12746         } else if(p = strstr(opt->name, " -button")) {
12747             opt->type = Button;
12748         } else if(p = strstr(opt->name, " -save")) {
12749             opt->type = SaveButton;
12750         } else return FALSE;
12751         *p = 0; // terminate option name
12752         // now look if the command-line options define a setting for this engine option.
12753         if(cps->optionSettings && cps->optionSettings[0])
12754             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12755         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12756                 sprintf(buf, "option %s", p);
12757                 if(p = strstr(buf, ",")) *p = 0;
12758                 strcat(buf, "\n");
12759                 SendToProgram(buf, cps);
12760         }
12761         return TRUE;
12762 }
12763
12764 void
12765 FeatureDone(cps, val)
12766      ChessProgramState* cps;
12767      int val;
12768 {
12769   DelayedEventCallback cb = GetDelayedEvent();
12770   if ((cb == InitBackEnd3 && cps == &first) ||
12771       (cb == TwoMachinesEventIfReady && cps == &second)) {
12772     CancelDelayedEvent();
12773     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12774   }
12775   cps->initDone = val;
12776 }
12777
12778 /* Parse feature command from engine */
12779 void
12780 ParseFeatures(args, cps)
12781      char* args;
12782      ChessProgramState *cps;  
12783 {
12784   char *p = args;
12785   char *q;
12786   int val;
12787   char buf[MSG_SIZ];
12788
12789   for (;;) {
12790     while (*p == ' ') p++;
12791     if (*p == NULLCHAR) return;
12792
12793     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12794     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12795     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12796     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12797     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12798     if (BoolFeature(&p, "reuse", &val, cps)) {
12799       /* Engine can disable reuse, but can't enable it if user said no */
12800       if (!val) cps->reuse = FALSE;
12801       continue;
12802     }
12803     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12804     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12805       if (gameMode == TwoMachinesPlay) {
12806         DisplayTwoMachinesTitle();
12807       } else {
12808         DisplayTitle("");
12809       }
12810       continue;
12811     }
12812     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12813     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12814     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12815     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12816     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12817     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12818     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12819     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12820     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12821     if (IntFeature(&p, "done", &val, cps)) {
12822       FeatureDone(cps, val);
12823       continue;
12824     }
12825     /* Added by Tord: */
12826     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12827     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12828     /* End of additions by Tord */
12829
12830     /* [HGM] added features: */
12831     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12832     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12833     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12834     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12835     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12836     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12837     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12838         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12839             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12840             SendToProgram(buf, cps);
12841             continue;
12842         }
12843         if(cps->nrOptions >= MAX_OPTIONS) {
12844             cps->nrOptions--;
12845             sprintf(buf, "%s engine has too many options\n", cps->which);
12846             DisplayError(buf, 0);
12847         }
12848         continue;
12849     }
12850     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12851     /* End of additions by HGM */
12852
12853     /* unknown feature: complain and skip */
12854     q = p;
12855     while (*q && *q != '=') q++;
12856     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12857     SendToProgram(buf, cps);
12858     p = q;
12859     if (*p == '=') {
12860       p++;
12861       if (*p == '\"') {
12862         p++;
12863         while (*p && *p != '\"') p++;
12864         if (*p == '\"') p++;
12865       } else {
12866         while (*p && *p != ' ') p++;
12867       }
12868     }
12869   }
12870
12871 }
12872
12873 void
12874 PeriodicUpdatesEvent(newState)
12875      int newState;
12876 {
12877     if (newState == appData.periodicUpdates)
12878       return;
12879
12880     appData.periodicUpdates=newState;
12881
12882     /* Display type changes, so update it now */
12883 //    DisplayAnalysis();
12884
12885     /* Get the ball rolling again... */
12886     if (newState) {
12887         AnalysisPeriodicEvent(1);
12888         StartAnalysisClock();
12889     }
12890 }
12891
12892 void
12893 PonderNextMoveEvent(newState)
12894      int newState;
12895 {
12896     if (newState == appData.ponderNextMove) return;
12897     if (gameMode == EditPosition) EditPositionDone(TRUE);
12898     if (newState) {
12899         SendToProgram("hard\n", &first);
12900         if (gameMode == TwoMachinesPlay) {
12901             SendToProgram("hard\n", &second);
12902         }
12903     } else {
12904         SendToProgram("easy\n", &first);
12905         thinkOutput[0] = NULLCHAR;
12906         if (gameMode == TwoMachinesPlay) {
12907             SendToProgram("easy\n", &second);
12908         }
12909     }
12910     appData.ponderNextMove = newState;
12911 }
12912
12913 void
12914 NewSettingEvent(option, command, value)
12915      char *command;
12916      int option, value;
12917 {
12918     char buf[MSG_SIZ];
12919
12920     if (gameMode == EditPosition) EditPositionDone(TRUE);
12921     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12922     SendToProgram(buf, &first);
12923     if (gameMode == TwoMachinesPlay) {
12924         SendToProgram(buf, &second);
12925     }
12926 }
12927
12928 void
12929 ShowThinkingEvent()
12930 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12931 {
12932     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12933     int newState = appData.showThinking
12934         // [HGM] thinking: other features now need thinking output as well
12935         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12936     
12937     if (oldState == newState) return;
12938     oldState = newState;
12939     if (gameMode == EditPosition) EditPositionDone(TRUE);
12940     if (oldState) {
12941         SendToProgram("post\n", &first);
12942         if (gameMode == TwoMachinesPlay) {
12943             SendToProgram("post\n", &second);
12944         }
12945     } else {
12946         SendToProgram("nopost\n", &first);
12947         thinkOutput[0] = NULLCHAR;
12948         if (gameMode == TwoMachinesPlay) {
12949             SendToProgram("nopost\n", &second);
12950         }
12951     }
12952 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12953 }
12954
12955 void
12956 AskQuestionEvent(title, question, replyPrefix, which)
12957      char *title; char *question; char *replyPrefix; char *which;
12958 {
12959   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12960   if (pr == NoProc) return;
12961   AskQuestion(title, question, replyPrefix, pr);
12962 }
12963
12964 void
12965 DisplayMove(moveNumber)
12966      int moveNumber;
12967 {
12968     char message[MSG_SIZ];
12969     char res[MSG_SIZ];
12970     char cpThinkOutput[MSG_SIZ];
12971
12972     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12973     
12974     if (moveNumber == forwardMostMove - 1 || 
12975         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12976
12977         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12978
12979         if (strchr(cpThinkOutput, '\n')) {
12980             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12981         }
12982     } else {
12983         *cpThinkOutput = NULLCHAR;
12984     }
12985
12986     /* [AS] Hide thinking from human user */
12987     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12988         *cpThinkOutput = NULLCHAR;
12989         if( thinkOutput[0] != NULLCHAR ) {
12990             int i;
12991
12992             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12993                 cpThinkOutput[i] = '.';
12994             }
12995             cpThinkOutput[i] = NULLCHAR;
12996             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12997         }
12998     }
12999
13000     if (moveNumber == forwardMostMove - 1 &&
13001         gameInfo.resultDetails != NULL) {
13002         if (gameInfo.resultDetails[0] == NULLCHAR) {
13003             sprintf(res, " %s", PGNResult(gameInfo.result));
13004         } else {
13005             sprintf(res, " {%s} %s",
13006                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13007         }
13008     } else {
13009         res[0] = NULLCHAR;
13010     }
13011
13012     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13013         DisplayMessage(res, cpThinkOutput);
13014     } else {
13015         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13016                 WhiteOnMove(moveNumber) ? " " : ".. ",
13017                 parseList[moveNumber], res);
13018         DisplayMessage(message, cpThinkOutput);
13019     }
13020 }
13021
13022 void
13023 DisplayComment(moveNumber, text)
13024      int moveNumber;
13025      char *text;
13026 {
13027     char title[MSG_SIZ];
13028     char buf[8000]; // comment can be long!
13029     int score, depth;
13030     
13031     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13032       strcpy(title, "Comment");
13033     } else {
13034       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13035               WhiteOnMove(moveNumber) ? " " : ".. ",
13036               parseList[moveNumber]);
13037     }
13038     // [HGM] PV info: display PV info together with (or as) comment
13039     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13040       if(text == NULL) text = "";                                           
13041       score = pvInfoList[moveNumber].score;
13042       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13043               depth, (pvInfoList[moveNumber].time+50)/100, text);
13044       text = buf;
13045     }
13046     if (text != NULL && (appData.autoDisplayComment || commentUp))
13047         CommentPopUp(title, text);
13048 }
13049
13050 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13051  * might be busy thinking or pondering.  It can be omitted if your
13052  * gnuchess is configured to stop thinking immediately on any user
13053  * input.  However, that gnuchess feature depends on the FIONREAD
13054  * ioctl, which does not work properly on some flavors of Unix.
13055  */
13056 void
13057 Attention(cps)
13058      ChessProgramState *cps;
13059 {
13060 #if ATTENTION
13061     if (!cps->useSigint) return;
13062     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13063     switch (gameMode) {
13064       case MachinePlaysWhite:
13065       case MachinePlaysBlack:
13066       case TwoMachinesPlay:
13067       case IcsPlayingWhite:
13068       case IcsPlayingBlack:
13069       case AnalyzeMode:
13070       case AnalyzeFile:
13071         /* Skip if we know it isn't thinking */
13072         if (!cps->maybeThinking) return;
13073         if (appData.debugMode)
13074           fprintf(debugFP, "Interrupting %s\n", cps->which);
13075         InterruptChildProcess(cps->pr);
13076         cps->maybeThinking = FALSE;
13077         break;
13078       default:
13079         break;
13080     }
13081 #endif /*ATTENTION*/
13082 }
13083
13084 int
13085 CheckFlags()
13086 {
13087     if (whiteTimeRemaining <= 0) {
13088         if (!whiteFlag) {
13089             whiteFlag = TRUE;
13090             if (appData.icsActive) {
13091                 if (appData.autoCallFlag &&
13092                     gameMode == IcsPlayingBlack && !blackFlag) {
13093                   SendToICS(ics_prefix);
13094                   SendToICS("flag\n");
13095                 }
13096             } else {
13097                 if (blackFlag) {
13098                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13099                 } else {
13100                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13101                     if (appData.autoCallFlag) {
13102                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13103                         return TRUE;
13104                     }
13105                 }
13106             }
13107         }
13108     }
13109     if (blackTimeRemaining <= 0) {
13110         if (!blackFlag) {
13111             blackFlag = TRUE;
13112             if (appData.icsActive) {
13113                 if (appData.autoCallFlag &&
13114                     gameMode == IcsPlayingWhite && !whiteFlag) {
13115                   SendToICS(ics_prefix);
13116                   SendToICS("flag\n");
13117                 }
13118             } else {
13119                 if (whiteFlag) {
13120                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13121                 } else {
13122                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13123                     if (appData.autoCallFlag) {
13124                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13125                         return TRUE;
13126                     }
13127                 }
13128             }
13129         }
13130     }
13131     return FALSE;
13132 }
13133
13134 void
13135 CheckTimeControl()
13136 {
13137     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13138         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13139
13140     /*
13141      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13142      */
13143     if ( !WhiteOnMove(forwardMostMove) )
13144         /* White made time control */
13145         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13146         /* [HGM] time odds: correct new time quota for time odds! */
13147                                             / WhitePlayer()->timeOdds;
13148       else
13149         /* Black made time control */
13150         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13151                                             / WhitePlayer()->other->timeOdds;
13152 }
13153
13154 void
13155 DisplayBothClocks()
13156 {
13157     int wom = gameMode == EditPosition ?
13158       !blackPlaysFirst : WhiteOnMove(currentMove);
13159     DisplayWhiteClock(whiteTimeRemaining, wom);
13160     DisplayBlackClock(blackTimeRemaining, !wom);
13161 }
13162
13163
13164 /* Timekeeping seems to be a portability nightmare.  I think everyone
13165    has ftime(), but I'm really not sure, so I'm including some ifdefs
13166    to use other calls if you don't.  Clocks will be less accurate if
13167    you have neither ftime nor gettimeofday.
13168 */
13169
13170 /* VS 2008 requires the #include outside of the function */
13171 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13172 #include <sys/timeb.h>
13173 #endif
13174
13175 /* Get the current time as a TimeMark */
13176 void
13177 GetTimeMark(tm)
13178      TimeMark *tm;
13179 {
13180 #if HAVE_GETTIMEOFDAY
13181
13182     struct timeval timeVal;
13183     struct timezone timeZone;
13184
13185     gettimeofday(&timeVal, &timeZone);
13186     tm->sec = (long) timeVal.tv_sec; 
13187     tm->ms = (int) (timeVal.tv_usec / 1000L);
13188
13189 #else /*!HAVE_GETTIMEOFDAY*/
13190 #if HAVE_FTIME
13191
13192 // include <sys/timeb.h> / moved to just above start of function
13193     struct timeb timeB;
13194
13195     ftime(&timeB);
13196     tm->sec = (long) timeB.time;
13197     tm->ms = (int) timeB.millitm;
13198
13199 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13200     tm->sec = (long) time(NULL);
13201     tm->ms = 0;
13202 #endif
13203 #endif
13204 }
13205
13206 /* Return the difference in milliseconds between two
13207    time marks.  We assume the difference will fit in a long!
13208 */
13209 long
13210 SubtractTimeMarks(tm2, tm1)
13211      TimeMark *tm2, *tm1;
13212 {
13213     return 1000L*(tm2->sec - tm1->sec) +
13214            (long) (tm2->ms - tm1->ms);
13215 }
13216
13217
13218 /*
13219  * Code to manage the game clocks.
13220  *
13221  * In tournament play, black starts the clock and then white makes a move.
13222  * We give the human user a slight advantage if he is playing white---the
13223  * clocks don't run until he makes his first move, so it takes zero time.
13224  * Also, we don't account for network lag, so we could get out of sync
13225  * with GNU Chess's clock -- but then, referees are always right.  
13226  */
13227
13228 static TimeMark tickStartTM;
13229 static long intendedTickLength;
13230
13231 long
13232 NextTickLength(timeRemaining)
13233      long timeRemaining;
13234 {
13235     long nominalTickLength, nextTickLength;
13236
13237     if (timeRemaining > 0L && timeRemaining <= 10000L)
13238       nominalTickLength = 100L;
13239     else
13240       nominalTickLength = 1000L;
13241     nextTickLength = timeRemaining % nominalTickLength;
13242     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13243
13244     return nextTickLength;
13245 }
13246
13247 /* Adjust clock one minute up or down */
13248 void
13249 AdjustClock(Boolean which, int dir)
13250 {
13251     if(which) blackTimeRemaining += 60000*dir;
13252     else      whiteTimeRemaining += 60000*dir;
13253     DisplayBothClocks();
13254 }
13255
13256 /* Stop clocks and reset to a fresh time control */
13257 void
13258 ResetClocks() 
13259 {
13260     (void) StopClockTimer();
13261     if (appData.icsActive) {
13262         whiteTimeRemaining = blackTimeRemaining = 0;
13263     } else if (searchTime) {
13264         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13265         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13266     } else { /* [HGM] correct new time quote for time odds */
13267         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13268         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13269     }
13270     if (whiteFlag || blackFlag) {
13271         DisplayTitle("");
13272         whiteFlag = blackFlag = FALSE;
13273     }
13274     DisplayBothClocks();
13275 }
13276
13277 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13278
13279 /* Decrement running clock by amount of time that has passed */
13280 void
13281 DecrementClocks()
13282 {
13283     long timeRemaining;
13284     long lastTickLength, fudge;
13285     TimeMark now;
13286
13287     if (!appData.clockMode) return;
13288     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13289         
13290     GetTimeMark(&now);
13291
13292     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13293
13294     /* Fudge if we woke up a little too soon */
13295     fudge = intendedTickLength - lastTickLength;
13296     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13297
13298     if (WhiteOnMove(forwardMostMove)) {
13299         if(whiteNPS >= 0) lastTickLength = 0;
13300         timeRemaining = whiteTimeRemaining -= lastTickLength;
13301         DisplayWhiteClock(whiteTimeRemaining - fudge,
13302                           WhiteOnMove(currentMove));
13303     } else {
13304         if(blackNPS >= 0) lastTickLength = 0;
13305         timeRemaining = blackTimeRemaining -= lastTickLength;
13306         DisplayBlackClock(blackTimeRemaining - fudge,
13307                           !WhiteOnMove(currentMove));
13308     }
13309
13310     if (CheckFlags()) return;
13311         
13312     tickStartTM = now;
13313     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13314     StartClockTimer(intendedTickLength);
13315
13316     /* if the time remaining has fallen below the alarm threshold, sound the
13317      * alarm. if the alarm has sounded and (due to a takeback or time control
13318      * with increment) the time remaining has increased to a level above the
13319      * threshold, reset the alarm so it can sound again. 
13320      */
13321     
13322     if (appData.icsActive && appData.icsAlarm) {
13323
13324         /* make sure we are dealing with the user's clock */
13325         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13326                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13327            )) return;
13328
13329         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13330             alarmSounded = FALSE;
13331         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13332             PlayAlarmSound();
13333             alarmSounded = TRUE;
13334         }
13335     }
13336 }
13337
13338
13339 /* A player has just moved, so stop the previously running
13340    clock and (if in clock mode) start the other one.
13341    We redisplay both clocks in case we're in ICS mode, because
13342    ICS gives us an update to both clocks after every move.
13343    Note that this routine is called *after* forwardMostMove
13344    is updated, so the last fractional tick must be subtracted
13345    from the color that is *not* on move now.
13346 */
13347 void
13348 SwitchClocks()
13349 {
13350     long lastTickLength;
13351     TimeMark now;
13352     int flagged = FALSE;
13353
13354     GetTimeMark(&now);
13355
13356     if (StopClockTimer() && appData.clockMode) {
13357         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13358         if (WhiteOnMove(forwardMostMove)) {
13359             if(blackNPS >= 0) lastTickLength = 0;
13360             blackTimeRemaining -= lastTickLength;
13361            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13362 //         if(pvInfoList[forwardMostMove-1].time == -1)
13363                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13364                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13365         } else {
13366            if(whiteNPS >= 0) lastTickLength = 0;
13367            whiteTimeRemaining -= lastTickLength;
13368            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13369 //         if(pvInfoList[forwardMostMove-1].time == -1)
13370                  pvInfoList[forwardMostMove-1].time = 
13371                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13372         }
13373         flagged = CheckFlags();
13374     }
13375     CheckTimeControl();
13376
13377     if (flagged || !appData.clockMode) return;
13378
13379     switch (gameMode) {
13380       case MachinePlaysBlack:
13381       case MachinePlaysWhite:
13382       case BeginningOfGame:
13383         if (pausing) return;
13384         break;
13385
13386       case EditGame:
13387       case PlayFromGameFile:
13388       case IcsExamining:
13389         return;
13390
13391       default:
13392         break;
13393     }
13394
13395     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13396         if(WhiteOnMove(forwardMostMove))
13397              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13398         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13399     }
13400
13401     tickStartTM = now;
13402     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13403       whiteTimeRemaining : blackTimeRemaining);
13404     StartClockTimer(intendedTickLength);
13405 }
13406         
13407
13408 /* Stop both clocks */
13409 void
13410 StopClocks()
13411 {       
13412     long lastTickLength;
13413     TimeMark now;
13414
13415     if (!StopClockTimer()) return;
13416     if (!appData.clockMode) return;
13417
13418     GetTimeMark(&now);
13419
13420     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13421     if (WhiteOnMove(forwardMostMove)) {
13422         if(whiteNPS >= 0) lastTickLength = 0;
13423         whiteTimeRemaining -= lastTickLength;
13424         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13425     } else {
13426         if(blackNPS >= 0) lastTickLength = 0;
13427         blackTimeRemaining -= lastTickLength;
13428         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13429     }
13430     CheckFlags();
13431 }
13432         
13433 /* Start clock of player on move.  Time may have been reset, so
13434    if clock is already running, stop and restart it. */
13435 void
13436 StartClocks()
13437 {
13438     (void) StopClockTimer(); /* in case it was running already */
13439     DisplayBothClocks();
13440     if (CheckFlags()) return;
13441
13442     if (!appData.clockMode) return;
13443     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13444
13445     GetTimeMark(&tickStartTM);
13446     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13447       whiteTimeRemaining : blackTimeRemaining);
13448
13449    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13450     whiteNPS = blackNPS = -1; 
13451     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13452        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13453         whiteNPS = first.nps;
13454     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13455        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13456         blackNPS = first.nps;
13457     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13458         whiteNPS = second.nps;
13459     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13460         blackNPS = second.nps;
13461     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13462
13463     StartClockTimer(intendedTickLength);
13464 }
13465
13466 char *
13467 TimeString(ms)
13468      long ms;
13469 {
13470     long second, minute, hour, day;
13471     char *sign = "";
13472     static char buf[32];
13473     
13474     if (ms > 0 && ms <= 9900) {
13475       /* convert milliseconds to tenths, rounding up */
13476       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13477
13478       sprintf(buf, " %03.1f ", tenths/10.0);
13479       return buf;
13480     }
13481
13482     /* convert milliseconds to seconds, rounding up */
13483     /* use floating point to avoid strangeness of integer division
13484        with negative dividends on many machines */
13485     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13486
13487     if (second < 0) {
13488         sign = "-";
13489         second = -second;
13490     }
13491     
13492     day = second / (60 * 60 * 24);
13493     second = second % (60 * 60 * 24);
13494     hour = second / (60 * 60);
13495     second = second % (60 * 60);
13496     minute = second / 60;
13497     second = second % 60;
13498     
13499     if (day > 0)
13500       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13501               sign, day, hour, minute, second);
13502     else if (hour > 0)
13503       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13504     else
13505       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13506     
13507     return buf;
13508 }
13509
13510
13511 /*
13512  * This is necessary because some C libraries aren't ANSI C compliant yet.
13513  */
13514 char *
13515 StrStr(string, match)
13516      char *string, *match;
13517 {
13518     int i, length;
13519     
13520     length = strlen(match);
13521     
13522     for (i = strlen(string) - length; i >= 0; i--, string++)
13523       if (!strncmp(match, string, length))
13524         return string;
13525     
13526     return NULL;
13527 }
13528
13529 char *
13530 StrCaseStr(string, match)
13531      char *string, *match;
13532 {
13533     int i, j, length;
13534     
13535     length = strlen(match);
13536     
13537     for (i = strlen(string) - length; i >= 0; i--, string++) {
13538         for (j = 0; j < length; j++) {
13539             if (ToLower(match[j]) != ToLower(string[j]))
13540               break;
13541         }
13542         if (j == length) return string;
13543     }
13544
13545     return NULL;
13546 }
13547
13548 #ifndef _amigados
13549 int
13550 StrCaseCmp(s1, s2)
13551      char *s1, *s2;
13552 {
13553     char c1, c2;
13554     
13555     for (;;) {
13556         c1 = ToLower(*s1++);
13557         c2 = ToLower(*s2++);
13558         if (c1 > c2) return 1;
13559         if (c1 < c2) return -1;
13560         if (c1 == NULLCHAR) return 0;
13561     }
13562 }
13563
13564
13565 int
13566 ToLower(c)
13567      int c;
13568 {
13569     return isupper(c) ? tolower(c) : c;
13570 }
13571
13572
13573 int
13574 ToUpper(c)
13575      int c;
13576 {
13577     return islower(c) ? toupper(c) : c;
13578 }
13579 #endif /* !_amigados    */
13580
13581 char *
13582 StrSave(s)
13583      char *s;
13584 {
13585     char *ret;
13586
13587     if ((ret = (char *) malloc(strlen(s) + 1))) {
13588         strcpy(ret, s);
13589     }
13590     return ret;
13591 }
13592
13593 char *
13594 StrSavePtr(s, savePtr)
13595      char *s, **savePtr;
13596 {
13597     if (*savePtr) {
13598         free(*savePtr);
13599     }
13600     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13601         strcpy(*savePtr, s);
13602     }
13603     return(*savePtr);
13604 }
13605
13606 char *
13607 PGNDate()
13608 {
13609     time_t clock;
13610     struct tm *tm;
13611     char buf[MSG_SIZ];
13612
13613     clock = time((time_t *)NULL);
13614     tm = localtime(&clock);
13615     sprintf(buf, "%04d.%02d.%02d",
13616             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13617     return StrSave(buf);
13618 }
13619
13620
13621 char *
13622 PositionToFEN(move, overrideCastling)
13623      int move;
13624      char *overrideCastling;
13625 {
13626     int i, j, fromX, fromY, toX, toY;
13627     int whiteToPlay;
13628     char buf[128];
13629     char *p, *q;
13630     int emptycount;
13631     ChessSquare piece;
13632
13633     whiteToPlay = (gameMode == EditPosition) ?
13634       !blackPlaysFirst : (move % 2 == 0);
13635     p = buf;
13636
13637     /* Piece placement data */
13638     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13639         emptycount = 0;
13640         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13641             if (boards[move][i][j] == EmptySquare) {
13642                 emptycount++;
13643             } else { ChessSquare piece = boards[move][i][j];
13644                 if (emptycount > 0) {
13645                     if(emptycount<10) /* [HGM] can be >= 10 */
13646                         *p++ = '0' + emptycount;
13647                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13648                     emptycount = 0;
13649                 }
13650                 if(PieceToChar(piece) == '+') {
13651                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13652                     *p++ = '+';
13653                     piece = (ChessSquare)(DEMOTED piece);
13654                 } 
13655                 *p++ = PieceToChar(piece);
13656                 if(p[-1] == '~') {
13657                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13658                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13659                     *p++ = '~';
13660                 }
13661             }
13662         }
13663         if (emptycount > 0) {
13664             if(emptycount<10) /* [HGM] can be >= 10 */
13665                 *p++ = '0' + emptycount;
13666             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13667             emptycount = 0;
13668         }
13669         *p++ = '/';
13670     }
13671     *(p - 1) = ' ';
13672
13673     /* [HGM] print Crazyhouse or Shogi holdings */
13674     if( gameInfo.holdingsWidth ) {
13675         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13676         q = p;
13677         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13678             piece = boards[move][i][BOARD_WIDTH-1];
13679             if( piece != EmptySquare )
13680               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13681                   *p++ = PieceToChar(piece);
13682         }
13683         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13684             piece = boards[move][BOARD_HEIGHT-i-1][0];
13685             if( piece != EmptySquare )
13686               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13687                   *p++ = PieceToChar(piece);
13688         }
13689
13690         if( q == p ) *p++ = '-';
13691         *p++ = ']';
13692         *p++ = ' ';
13693     }
13694
13695     /* Active color */
13696     *p++ = whiteToPlay ? 'w' : 'b';
13697     *p++ = ' ';
13698
13699   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13700     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13701   } else {
13702   if(nrCastlingRights) {
13703      q = p;
13704      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13705        /* [HGM] write directly from rights */
13706            if(boards[move][CASTLING][2] != NoRights &&
13707               boards[move][CASTLING][0] != NoRights   )
13708                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13709            if(boards[move][CASTLING][2] != NoRights &&
13710               boards[move][CASTLING][1] != NoRights   )
13711                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13712            if(boards[move][CASTLING][5] != NoRights &&
13713               boards[move][CASTLING][3] != NoRights   )
13714                 *p++ = boards[move][CASTLING][3] + AAA;
13715            if(boards[move][CASTLING][5] != NoRights &&
13716               boards[move][CASTLING][4] != NoRights   )
13717                 *p++ = boards[move][CASTLING][4] + AAA;
13718      } else {
13719
13720         /* [HGM] write true castling rights */
13721         if( nrCastlingRights == 6 ) {
13722             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13723                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13724             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13725                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13726             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13727                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13728             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13729                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13730         }
13731      }
13732      if (q == p) *p++ = '-'; /* No castling rights */
13733      *p++ = ' ';
13734   }
13735
13736   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13737      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13738     /* En passant target square */
13739     if (move > backwardMostMove) {
13740         fromX = moveList[move - 1][0] - AAA;
13741         fromY = moveList[move - 1][1] - ONE;
13742         toX = moveList[move - 1][2] - AAA;
13743         toY = moveList[move - 1][3] - ONE;
13744         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13745             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13746             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13747             fromX == toX) {
13748             /* 2-square pawn move just happened */
13749             *p++ = toX + AAA;
13750             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13751         } else {
13752             *p++ = '-';
13753         }
13754     } else if(move == backwardMostMove) {
13755         // [HGM] perhaps we should always do it like this, and forget the above?
13756         if((signed char)boards[move][EP_STATUS] >= 0) {
13757             *p++ = boards[move][EP_STATUS] + AAA;
13758             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13759         } else {
13760             *p++ = '-';
13761         }
13762     } else {
13763         *p++ = '-';
13764     }
13765     *p++ = ' ';
13766   }
13767   }
13768
13769     /* [HGM] find reversible plies */
13770     {   int i = 0, j=move;
13771
13772         if (appData.debugMode) { int k;
13773             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13774             for(k=backwardMostMove; k<=forwardMostMove; k++)
13775                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13776
13777         }
13778
13779         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13780         if( j == backwardMostMove ) i += initialRulePlies;
13781         sprintf(p, "%d ", i);
13782         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13783     }
13784     /* Fullmove number */
13785     sprintf(p, "%d", (move / 2) + 1);
13786     
13787     return StrSave(buf);
13788 }
13789
13790 Boolean
13791 ParseFEN(board, blackPlaysFirst, fen)
13792     Board board;
13793      int *blackPlaysFirst;
13794      char *fen;
13795 {
13796     int i, j;
13797     char *p;
13798     int emptycount;
13799     ChessSquare piece;
13800
13801     p = fen;
13802
13803     /* [HGM] by default clear Crazyhouse holdings, if present */
13804     if(gameInfo.holdingsWidth) {
13805        for(i=0; i<BOARD_HEIGHT; i++) {
13806            board[i][0]             = EmptySquare; /* black holdings */
13807            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13808            board[i][1]             = (ChessSquare) 0; /* black counts */
13809            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13810        }
13811     }
13812
13813     /* Piece placement data */
13814     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13815         j = 0;
13816         for (;;) {
13817             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13818                 if (*p == '/') p++;
13819                 emptycount = gameInfo.boardWidth - j;
13820                 while (emptycount--)
13821                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13822                 break;
13823 #if(BOARD_FILES >= 10)
13824             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13825                 p++; emptycount=10;
13826                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13827                 while (emptycount--)
13828                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13829 #endif
13830             } else if (isdigit(*p)) {
13831                 emptycount = *p++ - '0';
13832                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13833                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13834                 while (emptycount--)
13835                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13836             } else if (*p == '+' || isalpha(*p)) {
13837                 if (j >= gameInfo.boardWidth) return FALSE;
13838                 if(*p=='+') {
13839                     piece = CharToPiece(*++p);
13840                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13841                     piece = (ChessSquare) (PROMOTED piece ); p++;
13842                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13843                 } else piece = CharToPiece(*p++);
13844
13845                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13846                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13847                     piece = (ChessSquare) (PROMOTED piece);
13848                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13849                     p++;
13850                 }
13851                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13852             } else {
13853                 return FALSE;
13854             }
13855         }
13856     }
13857     while (*p == '/' || *p == ' ') p++;
13858
13859     /* [HGM] look for Crazyhouse holdings here */
13860     while(*p==' ') p++;
13861     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13862         if(*p == '[') p++;
13863         if(*p == '-' ) *p++; /* empty holdings */ else {
13864             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13865             /* if we would allow FEN reading to set board size, we would   */
13866             /* have to add holdings and shift the board read so far here   */
13867             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13868                 *p++;
13869                 if((int) piece >= (int) BlackPawn ) {
13870                     i = (int)piece - (int)BlackPawn;
13871                     i = PieceToNumber((ChessSquare)i);
13872                     if( i >= gameInfo.holdingsSize ) return FALSE;
13873                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13874                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13875                 } else {
13876                     i = (int)piece - (int)WhitePawn;
13877                     i = PieceToNumber((ChessSquare)i);
13878                     if( i >= gameInfo.holdingsSize ) return FALSE;
13879                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13880                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13881                 }
13882             }
13883         }
13884         if(*p == ']') *p++;
13885     }
13886
13887     while(*p == ' ') p++;
13888
13889     /* Active color */
13890     switch (*p++) {
13891       case 'w':
13892         *blackPlaysFirst = FALSE;
13893         break;
13894       case 'b': 
13895         *blackPlaysFirst = TRUE;
13896         break;
13897       default:
13898         return FALSE;
13899     }
13900
13901     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13902     /* return the extra info in global variiables             */
13903
13904     /* set defaults in case FEN is incomplete */
13905     board[EP_STATUS] = EP_UNKNOWN;
13906     for(i=0; i<nrCastlingRights; i++ ) {
13907         board[CASTLING][i] =
13908             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13909     }   /* assume possible unless obviously impossible */
13910     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13911     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13912     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13913     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13914     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13915     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13916     FENrulePlies = 0;
13917
13918     while(*p==' ') p++;
13919     if(nrCastlingRights) {
13920       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13921           /* castling indicator present, so default becomes no castlings */
13922           for(i=0; i<nrCastlingRights; i++ ) {
13923                  board[CASTLING][i] = NoRights;
13924           }
13925       }
13926       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13927              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13928              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13929              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13930         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13931
13932         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13933             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13934             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13935         }
13936         switch(c) {
13937           case'K':
13938               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13939               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
13940               board[CASTLING][2] = whiteKingFile;
13941               break;
13942           case'Q':
13943               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13944               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
13945               board[CASTLING][2] = whiteKingFile;
13946               break;
13947           case'k':
13948               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13949               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
13950               board[CASTLING][5] = blackKingFile;
13951               break;
13952           case'q':
13953               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13954               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
13955               board[CASTLING][5] = blackKingFile;
13956           case '-':
13957               break;
13958           default: /* FRC castlings */
13959               if(c >= 'a') { /* black rights */
13960                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13961                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13962                   if(i == BOARD_RGHT) break;
13963                   board[CASTLING][5] = i;
13964                   c -= AAA;
13965                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13966                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13967                   if(c > i)
13968                       board[CASTLING][3] = c;
13969                   else
13970                       board[CASTLING][4] = c;
13971               } else { /* white rights */
13972                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13973                     if(board[0][i] == WhiteKing) break;
13974                   if(i == BOARD_RGHT) break;
13975                   board[CASTLING][2] = i;
13976                   c -= AAA - 'a' + 'A';
13977                   if(board[0][c] >= WhiteKing) break;
13978                   if(c > i)
13979                       board[CASTLING][0] = c;
13980                   else
13981                       board[CASTLING][1] = c;
13982               }
13983         }
13984       }
13985     if (appData.debugMode) {
13986         fprintf(debugFP, "FEN castling rights:");
13987         for(i=0; i<nrCastlingRights; i++)
13988         fprintf(debugFP, " %d", board[CASTLING][i]);
13989         fprintf(debugFP, "\n");
13990     }
13991
13992       while(*p==' ') p++;
13993     }
13994
13995     /* read e.p. field in games that know e.p. capture */
13996     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13997        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13998       if(*p=='-') {
13999         p++; board[EP_STATUS] = EP_NONE;
14000       } else {
14001          char c = *p++ - AAA;
14002
14003          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14004          if(*p >= '0' && *p <='9') *p++;
14005          board[EP_STATUS] = c;
14006       }
14007     }
14008
14009
14010     if(sscanf(p, "%d", &i) == 1) {
14011         FENrulePlies = i; /* 50-move ply counter */
14012         /* (The move number is still ignored)    */
14013     }
14014
14015     return TRUE;
14016 }
14017       
14018 void
14019 EditPositionPasteFEN(char *fen)
14020 {
14021   if (fen != NULL) {
14022     Board initial_position;
14023
14024     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14025       DisplayError(_("Bad FEN position in clipboard"), 0);
14026       return ;
14027     } else {
14028       int savedBlackPlaysFirst = blackPlaysFirst;
14029       EditPositionEvent();
14030       blackPlaysFirst = savedBlackPlaysFirst;
14031       CopyBoard(boards[0], initial_position);
14032       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14033       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14034       DisplayBothClocks();
14035       DrawPosition(FALSE, boards[currentMove]);
14036     }
14037   }
14038 }
14039
14040 static char cseq[12] = "\\   ";
14041
14042 Boolean set_cont_sequence(char *new_seq)
14043 {
14044     int len;
14045     Boolean ret;
14046
14047     // handle bad attempts to set the sequence
14048         if (!new_seq)
14049                 return 0; // acceptable error - no debug
14050
14051     len = strlen(new_seq);
14052     ret = (len > 0) && (len < sizeof(cseq));
14053     if (ret)
14054         strcpy(cseq, new_seq);
14055     else if (appData.debugMode)
14056         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14057     return ret;
14058 }
14059
14060 /*
14061     reformat a source message so words don't cross the width boundary.  internal
14062     newlines are not removed.  returns the wrapped size (no null character unless
14063     included in source message).  If dest is NULL, only calculate the size required
14064     for the dest buffer.  lp argument indicats line position upon entry, and it's
14065     passed back upon exit.
14066 */
14067 int wrap(char *dest, char *src, int count, int width, int *lp)
14068 {
14069     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14070
14071     cseq_len = strlen(cseq);
14072     old_line = line = *lp;
14073     ansi = len = clen = 0;
14074
14075     for (i=0; i < count; i++)
14076     {
14077         if (src[i] == '\033')
14078             ansi = 1;
14079
14080         // if we hit the width, back up
14081         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14082         {
14083             // store i & len in case the word is too long
14084             old_i = i, old_len = len;
14085
14086             // find the end of the last word
14087             while (i && src[i] != ' ' && src[i] != '\n')
14088             {
14089                 i--;
14090                 len--;
14091             }
14092
14093             // word too long?  restore i & len before splitting it
14094             if ((old_i-i+clen) >= width)
14095             {
14096                 i = old_i;
14097                 len = old_len;
14098             }
14099
14100             // extra space?
14101             if (i && src[i-1] == ' ')
14102                 len--;
14103
14104             if (src[i] != ' ' && src[i] != '\n')
14105             {
14106                 i--;
14107                 if (len)
14108                     len--;
14109             }
14110
14111             // now append the newline and continuation sequence
14112             if (dest)
14113                 dest[len] = '\n';
14114             len++;
14115             if (dest)
14116                 strncpy(dest+len, cseq, cseq_len);
14117             len += cseq_len;
14118             line = cseq_len;
14119             clen = cseq_len;
14120             continue;
14121         }
14122
14123         if (dest)
14124             dest[len] = src[i];
14125         len++;
14126         if (!ansi)
14127             line++;
14128         if (src[i] == '\n')
14129             line = 0;
14130         if (src[i] == 'm')
14131             ansi = 0;
14132     }
14133     if (dest && appData.debugMode)
14134     {
14135         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14136             count, width, line, len, *lp);
14137         show_bytes(debugFP, src, count);
14138         fprintf(debugFP, "\ndest: ");
14139         show_bytes(debugFP, dest, len);
14140         fprintf(debugFP, "\n");
14141     }
14142     *lp = dest ? line : old_line;
14143
14144     return len;
14145 }
14146
14147 // [HGM] vari: routines for shelving variations
14148
14149 void 
14150 PushTail(int firstMove, int lastMove)
14151 {
14152         int i, j, nrMoves = lastMove - firstMove;
14153
14154         if(appData.icsActive) { // only in local mode
14155                 forwardMostMove = currentMove; // mimic old ICS behavior
14156                 return;
14157         }
14158         if(storedGames >= MAX_VARIATIONS-1) return;
14159
14160         // push current tail of game on stack
14161         savedResult[storedGames] = gameInfo.result;
14162         savedDetails[storedGames] = gameInfo.resultDetails;
14163         gameInfo.resultDetails = NULL;
14164         savedFirst[storedGames] = firstMove;
14165         savedLast [storedGames] = lastMove;
14166         savedFramePtr[storedGames] = framePtr;
14167         framePtr -= nrMoves; // reserve space for the boards
14168         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14169             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14170             for(j=0; j<MOVE_LEN; j++)
14171                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14172             for(j=0; j<2*MOVE_LEN; j++)
14173                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14174             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14175             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14176             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14177             pvInfoList[firstMove+i-1].depth = 0;
14178             commentList[framePtr+i] = commentList[firstMove+i];
14179             commentList[firstMove+i] = NULL;
14180         }
14181
14182         storedGames++;
14183         forwardMostMove = currentMove; // truncte game so we can start variation
14184         if(storedGames == 1) GreyRevert(FALSE);
14185 }
14186
14187 Boolean
14188 PopTail(Boolean annotate)
14189 {
14190         int i, j, nrMoves;
14191         char buf[8000], moveBuf[20];
14192
14193         if(appData.icsActive) return FALSE; // only in local mode
14194         if(!storedGames) return FALSE; // sanity
14195
14196         storedGames--;
14197         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14198         nrMoves = savedLast[storedGames] - currentMove;
14199         if(annotate) {
14200                 int cnt = 10;
14201                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14202                 else strcpy(buf, "(");
14203                 for(i=currentMove; i<forwardMostMove; i++) {
14204                         if(WhiteOnMove(i))
14205                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14206                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14207                         strcat(buf, moveBuf);
14208                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14209                 }
14210                 strcat(buf, ")");
14211         }
14212         for(i=1; i<nrMoves; i++) { // copy last variation back
14213             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14214             for(j=0; j<MOVE_LEN; j++)
14215                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14216             for(j=0; j<2*MOVE_LEN; j++)
14217                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14218             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14219             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14220             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14221             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14222             commentList[currentMove+i] = commentList[framePtr+i];
14223             commentList[framePtr+i] = NULL;
14224         }
14225         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14226         framePtr = savedFramePtr[storedGames];
14227         gameInfo.result = savedResult[storedGames];
14228         if(gameInfo.resultDetails != NULL) {
14229             free(gameInfo.resultDetails);
14230       }
14231         gameInfo.resultDetails = savedDetails[storedGames];
14232         forwardMostMove = currentMove + nrMoves;
14233         if(storedGames == 0) GreyRevert(TRUE);
14234         return TRUE;
14235 }
14236
14237 void 
14238 CleanupTail()
14239 {       // remove all shelved variations
14240         int i;
14241         for(i=0; i<storedGames; i++) {
14242             if(savedDetails[i])
14243                 free(savedDetails[i]);
14244             savedDetails[i] = NULL;
14245         }
14246         for(i=framePtr; i<MAX_MOVES; i++) {
14247                 if(commentList[i]) free(commentList[i]);
14248                 commentList[i] = NULL;
14249         }
14250         framePtr = MAX_MOVES-1;
14251         storedGames = 0;
14252 }