Match handles with multiple titles for channel Chat Boxes
[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, 2010 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, char *castle, char *ep));
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 KeepAlive P((void));
191 void StartClocks P((void));
192 void SwitchClocks P((int nr));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
221
222 #ifdef WIN32
223        extern void ConsoleCreate();
224 #endif
225
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
229
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
236
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 static int exiting = 0; /* [HGM] moved to top */
240 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
241 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
242 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
243 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
244 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
245 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
246 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
251 int chattingPartner;
252
253 /* States for ics_getting_history */
254 #define H_FALSE 0
255 #define H_REQUESTED 1
256 #define H_GOT_REQ_HEADER 2
257 #define H_GOT_UNREQ_HEADER 3
258 #define H_GETTING_MOVES 4
259 #define H_GOT_UNWANTED_HEADER 5
260
261 /* whosays values for GameEnds */
262 #define GE_ICS 0
263 #define GE_ENGINE 1
264 #define GE_PLAYER 2
265 #define GE_FILE 3
266 #define GE_XBOARD 4
267 #define GE_ENGINE1 5
268 #define GE_ENGINE2 6
269
270 /* Maximum number of games in a cmail message */
271 #define CMAIL_MAX_GAMES 20
272
273 /* Different types of move when calling RegisterMove */
274 #define CMAIL_MOVE   0
275 #define CMAIL_RESIGN 1
276 #define CMAIL_DRAW   2
277 #define CMAIL_ACCEPT 3
278
279 /* Different types of result to remember for each game */
280 #define CMAIL_NOT_RESULT 0
281 #define CMAIL_OLD_RESULT 1
282 #define CMAIL_NEW_RESULT 2
283
284 /* Telnet protocol constants */
285 #define TN_WILL 0373
286 #define TN_WONT 0374
287 #define TN_DO   0375
288 #define TN_DONT 0376
289 #define TN_IAC  0377
290 #define TN_ECHO 0001
291 #define TN_SGA  0003
292 #define TN_PORT 23
293
294 /* [AS] */
295 static char * safeStrCpy( char * dst, const char * src, size_t count )
296 {
297     assert( dst != NULL );
298     assert( src != NULL );
299     assert( count > 0 );
300
301     strncpy( dst, src, count );
302     dst[ count-1 ] = '\0';
303     return dst;
304 }
305
306 /* Some compiler can't cast u64 to double
307  * This function do the job for us:
308
309  * We use the highest bit for cast, this only
310  * works if the highest bit is not
311  * in use (This should not happen)
312  *
313  * We used this for all compiler
314  */
315 double
316 u64ToDouble(u64 value)
317 {
318   double r;
319   u64 tmp = value & u64Const(0x7fffffffffffffff);
320   r = (double)(s64)tmp;
321   if (value & u64Const(0x8000000000000000))
322        r +=  9.2233720368547758080e18; /* 2^63 */
323  return r;
324 }
325
326 /* Fake up flags for now, as we aren't keeping track of castling
327    availability yet. [HGM] Change of logic: the flag now only
328    indicates the type of castlings allowed by the rule of the game.
329    The actual rights themselves are maintained in the array
330    castlingRights, as part of the game history, and are not probed
331    by this function.
332  */
333 int
334 PosFlags(index)
335 {
336   int flags = F_ALL_CASTLE_OK;
337   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
338   switch (gameInfo.variant) {
339   case VariantSuicide:
340     flags &= ~F_ALL_CASTLE_OK;
341   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
342     flags |= F_IGNORE_CHECK;
343   case VariantLosers:
344     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345     break;
346   case VariantAtomic:
347     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
348     break;
349   case VariantKriegspiel:
350     flags |= F_KRIEGSPIEL_CAPTURE;
351     break;
352   case VariantCapaRandom: 
353   case VariantFischeRandom:
354     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
355   case VariantNoCastle:
356   case VariantShatranj:
357   case VariantCourier:
358   case VariantMakruk:
359     flags &= ~F_ALL_CASTLE_OK;
360     break;
361   default:
362     break;
363   }
364   return flags;
365 }
366
367 FILE *gameFileFP, *debugFP;
368
369 /* 
370     [AS] Note: sometimes, the sscanf() function is used to parse the input
371     into a fixed-size buffer. Because of this, we must be prepared to
372     receive strings as long as the size of the input buffer, which is currently
373     set to 4K for Windows and 8K for the rest.
374     So, we must either allocate sufficiently large buffers here, or
375     reduce the size of the input buffer in the input reading part.
376 */
377
378 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
379 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
380 char thinkOutput1[MSG_SIZ*10];
381
382 ChessProgramState first, second;
383
384 /* premove variables */
385 int premoveToX = 0;
386 int premoveToY = 0;
387 int premoveFromX = 0;
388 int premoveFromY = 0;
389 int premovePromoChar = 0;
390 int gotPremove = 0;
391 Boolean alarmSounded;
392 /* end premove variables */
393
394 char *ics_prefix = "$";
395 int ics_type = ICS_GENERIC;
396
397 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
398 int pauseExamForwardMostMove = 0;
399 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
400 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
401 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
402 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
403 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
404 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
405 int whiteFlag = FALSE, blackFlag = FALSE;
406 int userOfferedDraw = FALSE;
407 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
408 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
409 int cmailMoveType[CMAIL_MAX_GAMES];
410 long ics_clock_paused = 0;
411 ProcRef icsPR = NoProc, cmailPR = NoProc;
412 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
413 GameMode gameMode = BeginningOfGame;
414 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
415 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
416 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
417 int hiddenThinkOutputState = 0; /* [AS] */
418 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
419 int adjudicateLossPlies = 6;
420 char white_holding[64], black_holding[64];
421 TimeMark lastNodeCountTime;
422 long lastNodeCount=0;
423 int have_sent_ICS_logon = 0;
424 int movesPerSession;
425 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
426 long timeControl_2; /* [AS] Allow separate time controls */
427 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
428 long timeRemaining[2][MAX_MOVES];
429 int matchGame = 0;
430 TimeMark programStartTime;
431 char ics_handle[MSG_SIZ];
432 int have_set_title = 0;
433
434 /* animateTraining preserves the state of appData.animate
435  * when Training mode is activated. This allows the
436  * response to be animated when appData.animate == TRUE and
437  * appData.animateDragging == TRUE.
438  */
439 Boolean animateTraining;
440
441 GameInfo gameInfo;
442
443 AppData appData;
444
445 Board boards[MAX_MOVES];
446 /* [HGM] Following 7 needed for accurate legality tests: */
447 signed char  epStatus[MAX_MOVES];
448 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
449 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
450 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
451 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
452 int   initialRulePlies, FENrulePlies;
453 char  FENepStatus;
454 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
455 int loadFlag = 0; 
456 int shuffleOpenings;
457 int mute; // mute all sounds
458
459 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
460     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
461         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
462     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
463         BlackKing, BlackBishop, BlackKnight, BlackRook }
464 };
465
466 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
467     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
468         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
469     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
470         BlackKing, BlackKing, BlackKnight, BlackRook }
471 };
472
473 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
474     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
475         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
476     { BlackRook, BlackMan, BlackBishop, BlackQueen,
477         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
478 };
479
480 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] white and black different armies! */
481     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
483     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
484         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
485 };
486
487 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
488     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
489         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
490     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
491         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
492 };
493
494 ChessSquare makrukArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
495     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
496         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
497     { BlackRook, BlackKnight, BlackMan, BlackFerz,
498         BlackKing, BlackMan, BlackKnight, BlackRook }
499 };
500
501
502 #if (BOARD_SIZE>=10)
503 ChessSquare ShogiArray[2][BOARD_SIZE] = {
504     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
505         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
506     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
507         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
508 };
509
510 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
511     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
512         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
513     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
514         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
515 };
516
517 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
518     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
519         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
521         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
522 };
523
524 ChessSquare GreatArray[2][BOARD_SIZE] = {
525     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
526         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
527     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
528         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
529 };
530
531 ChessSquare JanusArray[2][BOARD_SIZE] = {
532     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
533         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
534     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
535         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
536 };
537
538 #ifdef GOTHIC
539 ChessSquare GothicArray[2][BOARD_SIZE] = {
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
541         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
543         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
544 };
545 #else // !GOTHIC
546 #define GothicArray CapablancaArray
547 #endif // !GOTHIC
548
549 #ifdef FALCON
550 ChessSquare FalconArray[2][BOARD_SIZE] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
552         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
554         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
555 };
556 #else // !FALCON
557 #define FalconArray CapablancaArray
558 #endif // !FALCON
559
560 #else // !(BOARD_SIZE>=10)
561 #define XiangqiPosition FIDEArray
562 #define CapablancaArray FIDEArray
563 #define GothicArray FIDEArray
564 #define GreatArray FIDEArray
565 #endif // !(BOARD_SIZE>=10)
566
567 #if (BOARD_SIZE>=12)
568 ChessSquare CourierArray[2][BOARD_SIZE] = {
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
570         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
571     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
572         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
573 };
574 #else // !(BOARD_SIZE>=12)
575 #define CourierArray CapablancaArray
576 #endif // !(BOARD_SIZE>=12)
577
578
579 Board initialPosition;
580
581
582 /* Convert str to a rating. Checks for special cases of "----",
583
584    "++++", etc. Also strips ()'s */
585 int
586 string_to_rating(str)
587   char *str;
588 {
589   while(*str && !isdigit(*str)) ++str;
590   if (!*str)
591     return 0;   /* One of the special "no rating" cases */
592   else
593     return atoi(str);
594 }
595
596 void
597 ClearProgramStats()
598 {
599     /* Init programStats */
600     programStats.movelist[0] = 0;
601     programStats.depth = 0;
602     programStats.nr_moves = 0;
603     programStats.moves_left = 0;
604     programStats.nodes = 0;
605     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
606     programStats.score = 0;
607     programStats.got_only_move = 0;
608     programStats.got_fail = 0;
609     programStats.line_is_book = 0;
610 }
611
612 void
613 InitBackEnd1()
614 {
615     int matched, min, sec;
616
617     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
618
619     GetTimeMark(&programStartTime);
620     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
621
622     ClearProgramStats();
623     programStats.ok_to_send = 1;
624     programStats.seen_stat = 0;
625
626     /*
627      * Initialize game list
628      */
629     ListNew(&gameList);
630
631
632     /*
633      * Internet chess server status
634      */
635     if (appData.icsActive) {
636         appData.matchMode = FALSE;
637         appData.matchGames = 0;
638 #if ZIPPY       
639         appData.noChessProgram = !appData.zippyPlay;
640 #else
641         appData.zippyPlay = FALSE;
642         appData.zippyTalk = FALSE;
643         appData.noChessProgram = TRUE;
644 #endif
645         if (*appData.icsHelper != NULLCHAR) {
646             appData.useTelnet = TRUE;
647             appData.telnetProgram = appData.icsHelper;
648         }
649     } else {
650         appData.zippyTalk = appData.zippyPlay = FALSE;
651     }
652
653     /* [AS] Initialize pv info list [HGM] and game state */
654     {
655         int i, j;
656
657         for( i=0; i<MAX_MOVES; i++ ) {
658             pvInfoList[i].depth = -1;
659             epStatus[i]=EP_NONE;
660             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
661         }
662     }
663
664     /*
665      * Parse timeControl resource
666      */
667     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
668                           appData.movesPerSession)) {
669         char buf[MSG_SIZ];
670         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
671         DisplayFatalError(buf, 0, 2);
672     }
673
674     /*
675      * Parse searchTime resource
676      */
677     if (*appData.searchTime != NULLCHAR) {
678         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
679         if (matched == 1) {
680             searchTime = min * 60;
681         } else if (matched == 2) {
682             searchTime = min * 60 + sec;
683         } else {
684             char buf[MSG_SIZ];
685             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
686             DisplayFatalError(buf, 0, 2);
687         }
688     }
689
690     /* [AS] Adjudication threshold */
691     adjudicateLossThreshold = appData.adjudicateLossThreshold;
692     
693     first.which = "first";
694     second.which = "second";
695     first.maybeThinking = second.maybeThinking = FALSE;
696     first.pr = second.pr = NoProc;
697     first.isr = second.isr = NULL;
698     first.sendTime = second.sendTime = 2;
699     first.sendDrawOffers = 1;
700     if (appData.firstPlaysBlack) {
701         first.twoMachinesColor = "black\n";
702         second.twoMachinesColor = "white\n";
703     } else {
704         first.twoMachinesColor = "white\n";
705         second.twoMachinesColor = "black\n";
706     }
707     first.program = appData.firstChessProgram;
708     second.program = appData.secondChessProgram;
709     first.host = appData.firstHost;
710     second.host = appData.secondHost;
711     first.dir = appData.firstDirectory;
712     second.dir = appData.secondDirectory;
713     first.other = &second;
714     second.other = &first;
715     first.initString = appData.initString;
716     second.initString = appData.secondInitString;
717     first.computerString = appData.firstComputerString;
718     second.computerString = appData.secondComputerString;
719     first.useSigint = second.useSigint = TRUE;
720     first.useSigterm = second.useSigterm = TRUE;
721     first.reuse = appData.reuseFirst;
722     second.reuse = appData.reuseSecond;
723     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
724     second.nps = appData.secondNPS;
725     first.useSetboard = second.useSetboard = FALSE;
726     first.useSAN = second.useSAN = FALSE;
727     first.usePing = second.usePing = FALSE;
728     first.lastPing = second.lastPing = 0;
729     first.lastPong = second.lastPong = 0;
730     first.usePlayother = second.usePlayother = FALSE;
731     first.useColors = second.useColors = TRUE;
732     first.useUsermove = second.useUsermove = FALSE;
733     first.sendICS = second.sendICS = FALSE;
734     first.sendName = second.sendName = appData.icsActive;
735     first.sdKludge = second.sdKludge = FALSE;
736     first.stKludge = second.stKludge = FALSE;
737     TidyProgramName(first.program, first.host, first.tidy);
738     TidyProgramName(second.program, second.host, second.tidy);
739     first.matchWins = second.matchWins = 0;
740     strcpy(first.variants, appData.variant);
741     strcpy(second.variants, appData.variant);
742     first.analysisSupport = second.analysisSupport = 2; /* detect */
743     first.analyzing = second.analyzing = FALSE;
744     first.initDone = second.initDone = FALSE;
745
746     /* New features added by Tord: */
747     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
748     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
749     /* End of new features added by Tord. */
750     first.fenOverride  = appData.fenOverride1;
751     second.fenOverride = appData.fenOverride2;
752
753     /* [HGM] time odds: set factor for each machine */
754     first.timeOdds  = appData.firstTimeOdds;
755     second.timeOdds = appData.secondTimeOdds;
756     { float norm = 1;
757         if(appData.timeOddsMode) {
758             norm = first.timeOdds;
759             if(norm > second.timeOdds) norm = second.timeOdds;
760         }
761         first.timeOdds /= norm;
762         second.timeOdds /= norm;
763     }
764
765     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
766     first.accumulateTC = appData.firstAccumulateTC;
767     second.accumulateTC = appData.secondAccumulateTC;
768     first.maxNrOfSessions = second.maxNrOfSessions = 1;
769
770     /* [HGM] debug */
771     first.debug = second.debug = FALSE;
772     first.supportsNPS = second.supportsNPS = UNKNOWN;
773
774     /* [HGM] options */
775     first.optionSettings  = appData.firstOptions;
776     second.optionSettings = appData.secondOptions;
777
778     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
779     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
780     first.isUCI = appData.firstIsUCI; /* [AS] */
781     second.isUCI = appData.secondIsUCI; /* [AS] */
782     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
783     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
784
785     if (appData.firstProtocolVersion > PROTOVER ||
786         appData.firstProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.firstProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       first.protocolVersion = appData.firstProtocolVersion;
793     }
794
795     if (appData.secondProtocolVersion > PROTOVER ||
796         appData.secondProtocolVersion < 1) {
797       char buf[MSG_SIZ];
798       sprintf(buf, _("protocol version %d not supported"),
799               appData.secondProtocolVersion);
800       DisplayFatalError(buf, 0, 2);
801     } else {
802       second.protocolVersion = appData.secondProtocolVersion;
803     }
804
805     if (appData.icsActive) {
806         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
807     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
808         appData.clockMode = FALSE;
809         first.sendTime = second.sendTime = 0;
810     }
811     
812 #if ZIPPY
813     /* Override some settings from environment variables, for backward
814        compatibility.  Unfortunately it's not feasible to have the env
815        vars just set defaults, at least in xboard.  Ugh.
816     */
817     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
818       ZippyInit();
819     }
820 #endif
821     
822     if (appData.noChessProgram) {
823         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
824         sprintf(programVersion, "%s", PACKAGE_STRING);
825     } else {
826       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
827       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
828       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
829     }
830
831     if (!appData.icsActive) {
832       char buf[MSG_SIZ];
833       /* Check for variants that are supported only in ICS mode,
834          or not at all.  Some that are accepted here nevertheless
835          have bugs; see comments below.
836       */
837       VariantClass variant = StringToVariant(appData.variant);
838       switch (variant) {
839       case VariantBughouse:     /* need four players and two boards */
840       case VariantKriegspiel:   /* need to hide pieces and move details */
841       /* case VariantFischeRandom: (Fabien: moved below) */
842         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
843         DisplayFatalError(buf, 0, 2);
844         return;
845
846       case VariantUnknown:
847       case VariantLoadable:
848       case Variant29:
849       case Variant30:
850       case Variant31:
851       case Variant32:
852       case Variant33:
853       case Variant34:
854       case Variant35:
855       case Variant36:
856       default:
857         sprintf(buf, _("Unknown variant name %s"), appData.variant);
858         DisplayFatalError(buf, 0, 2);
859         return;
860
861       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
862       case VariantFairy:      /* [HGM] TestLegality definitely off! */
863       case VariantGothic:     /* [HGM] should work */
864       case VariantCapablanca: /* [HGM] should work */
865       case VariantCourier:    /* [HGM] initial forced moves not implemented */
866       case VariantShogi:      /* [HGM] drops not tested for legality */
867       case VariantKnightmate: /* [HGM] should work */
868       case VariantCylinder:   /* [HGM] untested */
869       case VariantFalcon:     /* [HGM] untested */
870       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
871                                  offboard interposition not understood */
872       case VariantNormal:     /* definitely works! */
873       case VariantWildCastle: /* pieces not automatically shuffled */
874       case VariantNoCastle:   /* pieces not automatically shuffled */
875       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
876       case VariantLosers:     /* should work except for win condition,
877                                  and doesn't know captures are mandatory */
878       case VariantSuicide:    /* should work except for win condition,
879                                  and doesn't know captures are mandatory */
880       case VariantGiveaway:   /* should work except for win condition,
881                                  and doesn't know captures are mandatory */
882       case VariantTwoKings:   /* should work */
883       case VariantAtomic:     /* should work except for win condition */
884       case Variant3Check:     /* should work except for win condition */
885       case VariantShatranj:   /* should work except for all win conditions */
886       case VariantMakruk:     /* should work except for daw countdown */
887       case VariantBerolina:   /* might work if TestLegality is off */
888       case VariantCapaRandom: /* should work */
889       case VariantJanus:      /* should work */
890       case VariantSuper:      /* experimental */
891       case VariantGreat:      /* experimental, requires legality testing to be off */
892         break;
893       }
894     }
895
896     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
897     InitEngineUCI( installDir, &second );
898 }
899
900 int NextIntegerFromString( char ** str, long * value )
901 {
902     int result = -1;
903     char * s = *str;
904
905     while( *s == ' ' || *s == '\t' ) {
906         s++;
907     }
908
909     *value = 0;
910
911     if( *s >= '0' && *s <= '9' ) {
912         while( *s >= '0' && *s <= '9' ) {
913             *value = *value * 10 + (*s - '0');
914             s++;
915         }
916
917         result = 0;
918     }
919
920     *str = s;
921
922     return result;
923 }
924
925 int NextTimeControlFromString( char ** str, long * value )
926 {
927     long temp;
928     int result = NextIntegerFromString( str, &temp );
929
930     if( result == 0 ) {
931         *value = temp * 60; /* Minutes */
932         if( **str == ':' ) {
933             (*str)++;
934             result = NextIntegerFromString( str, &temp );
935             *value += temp; /* Seconds */
936         }
937     }
938
939     return result;
940 }
941
942 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
943 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
944     int result = -1; long temp, temp2;
945
946     if(**str != '+') return -1; // old params remain in force!
947     (*str)++;
948     if( NextTimeControlFromString( str, &temp ) ) return -1;
949
950     if(**str != '/') {
951         /* time only: incremental or sudden-death time control */
952         if(**str == '+') { /* increment follows; read it */
953             (*str)++;
954             if(result = NextIntegerFromString( str, &temp2)) return -1;
955             *inc = temp2 * 1000;
956         } else *inc = 0;
957         *moves = 0; *tc = temp * 1000; 
958         return 0;
959     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
960
961     (*str)++; /* classical time control */
962     result = NextTimeControlFromString( str, &temp2);
963     if(result == 0) {
964         *moves = temp/60;
965         *tc    = temp2 * 1000;
966         *inc   = 0;
967     }
968     return result;
969 }
970
971 int GetTimeQuota(int movenr)
972 {   /* [HGM] get time to add from the multi-session time-control string */
973     int moves=1; /* kludge to force reading of first session */
974     long time, increment;
975     char *s = fullTimeControlString;
976
977     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
978     do {
979         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
980         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
981         if(movenr == -1) return time;    /* last move before new session     */
982         if(!moves) return increment;     /* current session is incremental   */
983         if(movenr >= 0) movenr -= moves; /* we already finished this session */
984     } while(movenr >= -1);               /* try again for next session       */
985
986     return 0; // no new time quota on this move
987 }
988
989 int
990 ParseTimeControl(tc, ti, mps)
991      char *tc;
992      int ti;
993      int mps;
994 {
995   long tc1;
996   long tc2;
997   char buf[MSG_SIZ];
998   
999   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1000   if(ti > 0) {
1001     if(mps)
1002       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1003     else sprintf(buf, "+%s+%d", tc, ti);
1004   } else {
1005     if(mps)
1006              sprintf(buf, "+%d/%s", mps, tc);
1007     else sprintf(buf, "+%s", tc);
1008   }
1009   fullTimeControlString = StrSave(buf);
1010   
1011   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1012     return FALSE;
1013   }
1014   
1015   if( *tc == '/' ) {
1016     /* Parse second time control */
1017     tc++;
1018     
1019     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1020       return FALSE;
1021     }
1022     
1023     if( tc2 == 0 ) {
1024       return FALSE;
1025     }
1026     
1027     timeControl_2 = tc2 * 1000;
1028   }
1029   else {
1030     timeControl_2 = 0;
1031   }
1032   
1033   if( tc1 == 0 ) {
1034     return FALSE;
1035   }
1036   
1037   timeControl = tc1 * 1000;
1038   
1039   if (ti >= 0) {
1040     timeIncrement = ti * 1000;  /* convert to ms */
1041     movesPerSession = 0;
1042   } else {
1043     timeIncrement = 0;
1044     movesPerSession = mps;
1045   }
1046   return TRUE;
1047 }
1048
1049 void
1050 InitBackEnd2()
1051 {
1052     if (appData.debugMode) {
1053         fprintf(debugFP, "%s\n", programVersion);
1054     }
1055
1056     set_cont_sequence(appData.wrapContSeq);
1057     if (appData.matchGames > 0) {
1058         appData.matchMode = TRUE;
1059     } else if (appData.matchMode) {
1060         appData.matchGames = 1;
1061     }
1062     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1063         appData.matchGames = appData.sameColorGames;
1064     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1065         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1066         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1067     }
1068     Reset(TRUE, FALSE);
1069     if (appData.noChessProgram || first.protocolVersion == 1) {
1070       InitBackEnd3();
1071     } else {
1072       /* kludge: allow timeout for initial "feature" commands */
1073       FreezeUI();
1074       DisplayMessage("", _("Starting chess program"));
1075       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1076     }
1077 }
1078
1079 void
1080 InitBackEnd3 P((void))
1081 {
1082     GameMode initialMode;
1083     char buf[MSG_SIZ];
1084     int err;
1085
1086     InitChessProgram(&first, startedFromSetupPosition);
1087
1088
1089     if (appData.icsActive) {
1090 #ifdef WIN32
1091         /* [DM] Make a console window if needed [HGM] merged ifs */
1092         ConsoleCreate(); 
1093 #endif
1094         err = establish();
1095         if (err != 0) {
1096             if (*appData.icsCommPort != NULLCHAR) {
1097                 sprintf(buf, _("Could not open comm port %s"),  
1098                         appData.icsCommPort);
1099             } else {
1100                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1101                         appData.icsHost, appData.icsPort);
1102             }
1103             DisplayFatalError(buf, err, 1);
1104             return;
1105         }
1106         SetICSMode();
1107         telnetISR =
1108           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1109         fromUserISR =
1110           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1111         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1112             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1113     } else if (appData.noChessProgram) {
1114         SetNCPMode();
1115     } else {
1116         SetGNUMode();
1117     }
1118
1119     if (*appData.cmailGameName != NULLCHAR) {
1120         SetCmailMode();
1121         OpenLoopback(&cmailPR);
1122         cmailISR =
1123           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1124     }
1125     
1126     ThawUI();
1127     DisplayMessage("", "");
1128     if (StrCaseCmp(appData.initialMode, "") == 0) {
1129       initialMode = BeginningOfGame;
1130     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1131       initialMode = TwoMachinesPlay;
1132     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1133       initialMode = AnalyzeFile; 
1134     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1135       initialMode = AnalyzeMode;
1136     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1137       initialMode = MachinePlaysWhite;
1138     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1139       initialMode = MachinePlaysBlack;
1140     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1141       initialMode = EditGame;
1142     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1143       initialMode = EditPosition;
1144     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1145       initialMode = Training;
1146     } else {
1147       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1148       DisplayFatalError(buf, 0, 2);
1149       return;
1150     }
1151
1152     if (appData.matchMode) {
1153         /* Set up machine vs. machine match */
1154         if (appData.noChessProgram) {
1155             DisplayFatalError(_("Can't have a match with no chess programs"),
1156                               0, 2);
1157             return;
1158         }
1159         matchMode = TRUE;
1160         matchGame = 1;
1161         if (*appData.loadGameFile != NULLCHAR) {
1162             int index = appData.loadGameIndex; // [HGM] autoinc
1163             if(index<0) lastIndex = index = 1;
1164             if (!LoadGameFromFile(appData.loadGameFile,
1165                                   index,
1166                                   appData.loadGameFile, FALSE)) {
1167                 DisplayFatalError(_("Bad game file"), 0, 1);
1168                 return;
1169             }
1170         } else if (*appData.loadPositionFile != NULLCHAR) {
1171             int index = appData.loadPositionIndex; // [HGM] autoinc
1172             if(index<0) lastIndex = index = 1;
1173             if (!LoadPositionFromFile(appData.loadPositionFile,
1174                                       index,
1175                                       appData.loadPositionFile)) {
1176                 DisplayFatalError(_("Bad position file"), 0, 1);
1177                 return;
1178             }
1179         }
1180         TwoMachinesEvent();
1181     } else if (*appData.cmailGameName != NULLCHAR) {
1182         /* Set up cmail mode */
1183         ReloadCmailMsgEvent(TRUE);
1184     } else {
1185         /* Set up other modes */
1186         if (initialMode == AnalyzeFile) {
1187           if (*appData.loadGameFile == NULLCHAR) {
1188             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1189             return;
1190           }
1191         }
1192         if (*appData.loadGameFile != NULLCHAR) {
1193             (void) LoadGameFromFile(appData.loadGameFile,
1194                                     appData.loadGameIndex,
1195                                     appData.loadGameFile, TRUE);
1196         } else if (*appData.loadPositionFile != NULLCHAR) {
1197             (void) LoadPositionFromFile(appData.loadPositionFile,
1198                                         appData.loadPositionIndex,
1199                                         appData.loadPositionFile);
1200             /* [HGM] try to make self-starting even after FEN load */
1201             /* to allow automatic setup of fairy variants with wtm */
1202             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1203                 gameMode = BeginningOfGame;
1204                 setboardSpoiledMachineBlack = 1;
1205             }
1206             /* [HGM] loadPos: make that every new game uses the setup */
1207             /* from file as long as we do not switch variant          */
1208             if(!blackPlaysFirst) { int i;
1209                 startedFromPositionFile = TRUE;
1210                 CopyBoard(filePosition, boards[0]);
1211                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1212             }
1213         }
1214         if (initialMode == AnalyzeMode) {
1215           if (appData.noChessProgram) {
1216             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1217             return;
1218           }
1219           if (appData.icsActive) {
1220             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1221             return;
1222           }
1223           AnalyzeModeEvent();
1224         } else if (initialMode == AnalyzeFile) {
1225           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1226           ShowThinkingEvent();
1227           AnalyzeFileEvent();
1228           AnalysisPeriodicEvent(1);
1229         } else if (initialMode == MachinePlaysWhite) {
1230           if (appData.noChessProgram) {
1231             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1232                               0, 2);
1233             return;
1234           }
1235           if (appData.icsActive) {
1236             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1237                               0, 2);
1238             return;
1239           }
1240           MachineWhiteEvent();
1241         } else if (initialMode == MachinePlaysBlack) {
1242           if (appData.noChessProgram) {
1243             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1244                               0, 2);
1245             return;
1246           }
1247           if (appData.icsActive) {
1248             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1249                               0, 2);
1250             return;
1251           }
1252           MachineBlackEvent();
1253         } else if (initialMode == TwoMachinesPlay) {
1254           if (appData.noChessProgram) {
1255             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1256                               0, 2);
1257             return;
1258           }
1259           if (appData.icsActive) {
1260             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1261                               0, 2);
1262             return;
1263           }
1264           TwoMachinesEvent();
1265         } else if (initialMode == EditGame) {
1266           EditGameEvent();
1267         } else if (initialMode == EditPosition) {
1268           EditPositionEvent();
1269         } else if (initialMode == Training) {
1270           if (*appData.loadGameFile == NULLCHAR) {
1271             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1272             return;
1273           }
1274           TrainingEvent();
1275         }
1276     }
1277 }
1278
1279 /*
1280  * Establish will establish a contact to a remote host.port.
1281  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1282  *  used to talk to the host.
1283  * Returns 0 if okay, error code if not.
1284  */
1285 int
1286 establish()
1287 {
1288     char buf[MSG_SIZ];
1289
1290     if (*appData.icsCommPort != NULLCHAR) {
1291         /* Talk to the host through a serial comm port */
1292         return OpenCommPort(appData.icsCommPort, &icsPR);
1293
1294     } else if (*appData.gateway != NULLCHAR) {
1295         if (*appData.remoteShell == NULLCHAR) {
1296             /* Use the rcmd protocol to run telnet program on a gateway host */
1297             snprintf(buf, sizeof(buf), "%s %s %s",
1298                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1299             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1300
1301         } else {
1302             /* Use the rsh program to run telnet program on a gateway host */
1303             if (*appData.remoteUser == NULLCHAR) {
1304                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1305                         appData.gateway, appData.telnetProgram,
1306                         appData.icsHost, appData.icsPort);
1307             } else {
1308                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1309                         appData.remoteShell, appData.gateway, 
1310                         appData.remoteUser, appData.telnetProgram,
1311                         appData.icsHost, appData.icsPort);
1312             }
1313             return StartChildProcess(buf, "", &icsPR);
1314
1315         }
1316     } else if (appData.useTelnet) {
1317         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1318
1319     } else {
1320         /* TCP socket interface differs somewhat between
1321            Unix and NT; handle details in the front end.
1322            */
1323         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1324     }
1325 }
1326
1327 void
1328 show_bytes(fp, buf, count)
1329      FILE *fp;
1330      char *buf;
1331      int count;
1332 {
1333     while (count--) {
1334         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1335             fprintf(fp, "\\%03o", *buf & 0xff);
1336         } else {
1337             putc(*buf, fp);
1338         }
1339         buf++;
1340     }
1341     fflush(fp);
1342 }
1343
1344 /* Returns an errno value */
1345 int
1346 OutputMaybeTelnet(pr, message, count, outError)
1347      ProcRef pr;
1348      char *message;
1349      int count;
1350      int *outError;
1351 {
1352     char buf[8192], *p, *q, *buflim;
1353     int left, newcount, outcount;
1354
1355     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1356         *appData.gateway != NULLCHAR) {
1357         if (appData.debugMode) {
1358             fprintf(debugFP, ">ICS: ");
1359             show_bytes(debugFP, message, count);
1360             fprintf(debugFP, "\n");
1361         }
1362         return OutputToProcess(pr, message, count, outError);
1363     }
1364
1365     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1366     p = message;
1367     q = buf;
1368     left = count;
1369     newcount = 0;
1370     while (left) {
1371         if (q >= buflim) {
1372             if (appData.debugMode) {
1373                 fprintf(debugFP, ">ICS: ");
1374                 show_bytes(debugFP, buf, newcount);
1375                 fprintf(debugFP, "\n");
1376             }
1377             outcount = OutputToProcess(pr, buf, newcount, outError);
1378             if (outcount < newcount) return -1; /* to be sure */
1379             q = buf;
1380             newcount = 0;
1381         }
1382         if (*p == '\n') {
1383             *q++ = '\r';
1384             newcount++;
1385         } else if (((unsigned char) *p) == TN_IAC) {
1386             *q++ = (char) TN_IAC;
1387             newcount ++;
1388         }
1389         *q++ = *p++;
1390         newcount++;
1391         left--;
1392     }
1393     if (appData.debugMode) {
1394         fprintf(debugFP, ">ICS: ");
1395         show_bytes(debugFP, buf, newcount);
1396         fprintf(debugFP, "\n");
1397     }
1398     outcount = OutputToProcess(pr, buf, newcount, outError);
1399     if (outcount < newcount) return -1; /* to be sure */
1400     return count;
1401 }
1402
1403 void
1404 read_from_player(isr, closure, message, count, error)
1405      InputSourceRef isr;
1406      VOIDSTAR closure;
1407      char *message;
1408      int count;
1409      int error;
1410 {
1411     int outError, outCount;
1412     static int gotEof = 0;
1413
1414     /* Pass data read from player on to ICS */
1415     if (count > 0) {
1416         gotEof = 0;
1417         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1418         if (outCount < count) {
1419             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1420         }
1421     } else if (count < 0) {
1422         RemoveInputSource(isr);
1423         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1424     } else if (gotEof++ > 0) {
1425         RemoveInputSource(isr);
1426         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1427     }
1428 }
1429
1430 void
1431 KeepAlive()
1432 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1433     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1434     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1435     SendToICS("date\n");
1436     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1437 }
1438
1439 /* added routine for printf style output to ics */
1440 void ics_printf(char *format, ...)
1441 {
1442     char buffer[MSG_SIZ];
1443     va_list args;
1444
1445     va_start(args, format);
1446     vsnprintf(buffer, sizeof(buffer), format, args);
1447     buffer[sizeof(buffer)-1] = '\0';
1448     SendToICS(buffer);
1449     va_end(args);
1450 }
1451
1452 void
1453 SendToICS(s)
1454      char *s;
1455 {
1456     int count, outCount, outError;
1457
1458     if (icsPR == NULL) return;
1459
1460     count = strlen(s);
1461     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1462     if (outCount < count) {
1463         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1464     }
1465 }
1466
1467 /* This is used for sending logon scripts to the ICS. Sending
1468    without a delay causes problems when using timestamp on ICC
1469    (at least on my machine). */
1470 void
1471 SendToICSDelayed(s,msdelay)
1472      char *s;
1473      long msdelay;
1474 {
1475     int count, outCount, outError;
1476
1477     if (icsPR == NULL) return;
1478
1479     count = strlen(s);
1480     if (appData.debugMode) {
1481         fprintf(debugFP, ">ICS: ");
1482         show_bytes(debugFP, s, count);
1483         fprintf(debugFP, "\n");
1484     }
1485     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1486                                       msdelay);
1487     if (outCount < count) {
1488         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1489     }
1490 }
1491
1492
1493 /* Remove all highlighting escape sequences in s
1494    Also deletes any suffix starting with '(' 
1495    */
1496 char *
1497 StripHighlightAndTitle(s)
1498      char *s;
1499 {
1500     static char retbuf[MSG_SIZ];
1501     char *p = retbuf;
1502
1503     while (*s != NULLCHAR) {
1504         while (*s == '\033') {
1505             while (*s != NULLCHAR && !isalpha(*s)) s++;
1506             if (*s != NULLCHAR) s++;
1507         }
1508         while (*s != NULLCHAR && *s != '\033') {
1509             if (*s == '(' || *s == '[') {
1510                 *p = NULLCHAR;
1511                 return retbuf;
1512             }
1513             *p++ = *s++;
1514         }
1515     }
1516     *p = NULLCHAR;
1517     return retbuf;
1518 }
1519
1520 /* Remove all highlighting escape sequences in s */
1521 char *
1522 StripHighlight(s)
1523      char *s;
1524 {
1525     static char retbuf[MSG_SIZ];
1526     char *p = retbuf;
1527
1528     while (*s != NULLCHAR) {
1529         while (*s == '\033') {
1530             while (*s != NULLCHAR && !isalpha(*s)) s++;
1531             if (*s != NULLCHAR) s++;
1532         }
1533         while (*s != NULLCHAR && *s != '\033') {
1534             *p++ = *s++;
1535         }
1536     }
1537     *p = NULLCHAR;
1538     return retbuf;
1539 }
1540
1541 char *variantNames[] = VARIANT_NAMES;
1542 char *
1543 VariantName(v)
1544      VariantClass v;
1545 {
1546     return variantNames[v];
1547 }
1548
1549
1550 /* Identify a variant from the strings the chess servers use or the
1551    PGN Variant tag names we use. */
1552 VariantClass
1553 StringToVariant(e)
1554      char *e;
1555 {
1556     char *p;
1557     int wnum = -1;
1558     VariantClass v = VariantNormal;
1559     int i, found = FALSE;
1560     char buf[MSG_SIZ];
1561
1562     if (!e) return v;
1563
1564     /* [HGM] skip over optional board-size prefixes */
1565     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1566         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1567         while( *e++ != '_');
1568     }
1569
1570     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1571         v = VariantNormal;
1572         found = TRUE;
1573     } else
1574     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1575       if (StrCaseStr(e, variantNames[i])) {
1576         v = (VariantClass) i;
1577         found = TRUE;
1578         break;
1579       }
1580     }
1581
1582     if (!found) {
1583       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1584           || StrCaseStr(e, "wild/fr") 
1585           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1586         v = VariantFischeRandom;
1587       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1588                  (i = 1, p = StrCaseStr(e, "w"))) {
1589         p += i;
1590         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1591         if (isdigit(*p)) {
1592           wnum = atoi(p);
1593         } else {
1594           wnum = -1;
1595         }
1596         switch (wnum) {
1597         case 0: /* FICS only, actually */
1598         case 1:
1599           /* Castling legal even if K starts on d-file */
1600           v = VariantWildCastle;
1601           break;
1602         case 2:
1603         case 3:
1604         case 4:
1605           /* Castling illegal even if K & R happen to start in
1606              normal positions. */
1607           v = VariantNoCastle;
1608           break;
1609         case 5:
1610         case 7:
1611         case 8:
1612         case 10:
1613         case 11:
1614         case 12:
1615         case 13:
1616         case 14:
1617         case 15:
1618         case 18:
1619         case 19:
1620           /* Castling legal iff K & R start in normal positions */
1621           v = VariantNormal;
1622           break;
1623         case 6:
1624         case 20:
1625         case 21:
1626           /* Special wilds for position setup; unclear what to do here */
1627           v = VariantLoadable;
1628           break;
1629         case 9:
1630           /* Bizarre ICC game */
1631           v = VariantTwoKings;
1632           break;
1633         case 16:
1634           v = VariantKriegspiel;
1635           break;
1636         case 17:
1637           v = VariantLosers;
1638           break;
1639         case 22:
1640           v = VariantFischeRandom;
1641           break;
1642         case 23:
1643           v = VariantCrazyhouse;
1644           break;
1645         case 24:
1646           v = VariantBughouse;
1647           break;
1648         case 25:
1649           v = Variant3Check;
1650           break;
1651         case 26:
1652           /* Not quite the same as FICS suicide! */
1653           v = VariantGiveaway;
1654           break;
1655         case 27:
1656           v = VariantAtomic;
1657           break;
1658         case 28:
1659           v = VariantShatranj;
1660           break;
1661
1662         /* Temporary names for future ICC types.  The name *will* change in 
1663            the next xboard/WinBoard release after ICC defines it. */
1664         case 29:
1665           v = Variant29;
1666           break;
1667         case 30:
1668           v = Variant30;
1669           break;
1670         case 31:
1671           v = Variant31;
1672           break;
1673         case 32:
1674           v = Variant32;
1675           break;
1676         case 33:
1677           v = Variant33;
1678           break;
1679         case 34:
1680           v = Variant34;
1681           break;
1682         case 35:
1683           v = Variant35;
1684           break;
1685         case 36:
1686           v = Variant36;
1687           break;
1688         case 37:
1689           v = VariantShogi;
1690           break;
1691         case 38:
1692           v = VariantXiangqi;
1693           break;
1694         case 39:
1695           v = VariantCourier;
1696           break;
1697         case 40:
1698           v = VariantGothic;
1699           break;
1700         case 41:
1701           v = VariantCapablanca;
1702           break;
1703         case 42:
1704           v = VariantKnightmate;
1705           break;
1706         case 43:
1707           v = VariantFairy;
1708           break;
1709         case 44:
1710           v = VariantCylinder;
1711           break;
1712         case 45:
1713           v = VariantFalcon;
1714           break;
1715         case 46:
1716           v = VariantCapaRandom;
1717           break;
1718         case 47:
1719           v = VariantBerolina;
1720           break;
1721         case 48:
1722           v = VariantJanus;
1723           break;
1724         case 49:
1725           v = VariantSuper;
1726           break;
1727         case 50:
1728           v = VariantGreat;
1729           break;
1730         case -1:
1731           /* Found "wild" or "w" in the string but no number;
1732              must assume it's normal chess. */
1733           v = VariantNormal;
1734           break;
1735         default:
1736           sprintf(buf, _("Unknown wild type %d"), wnum);
1737           DisplayError(buf, 0);
1738           v = VariantUnknown;
1739           break;
1740         }
1741       }
1742     }
1743     if (appData.debugMode) {
1744       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1745               e, wnum, VariantName(v));
1746     }
1747     return v;
1748 }
1749
1750 static int leftover_start = 0, leftover_len = 0;
1751 char star_match[STAR_MATCH_N][MSG_SIZ];
1752
1753 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1754    advance *index beyond it, and set leftover_start to the new value of
1755    *index; else return FALSE.  If pattern contains the character '*', it
1756    matches any sequence of characters not containing '\r', '\n', or the
1757    character following the '*' (if any), and the matched sequence(s) are
1758    copied into star_match.
1759    */
1760 int
1761 looking_at(buf, index, pattern)
1762      char *buf;
1763      int *index;
1764      char *pattern;
1765 {
1766     char *bufp = &buf[*index], *patternp = pattern;
1767     int star_count = 0;
1768     char *matchp = star_match[0];
1769     
1770     for (;;) {
1771         if (*patternp == NULLCHAR) {
1772             *index = leftover_start = bufp - buf;
1773             *matchp = NULLCHAR;
1774             return TRUE;
1775         }
1776         if (*bufp == NULLCHAR) return FALSE;
1777         if (*patternp == '*') {
1778             if (*bufp == *(patternp + 1)) {
1779                 *matchp = NULLCHAR;
1780                 matchp = star_match[++star_count];
1781                 patternp += 2;
1782                 bufp++;
1783                 continue;
1784             } else if (*bufp == '\n' || *bufp == '\r') {
1785                 patternp++;
1786                 if (*patternp == NULLCHAR)
1787                   continue;
1788                 else
1789                   return FALSE;
1790             } else {
1791                 *matchp++ = *bufp++;
1792                 continue;
1793             }
1794         }
1795         if (*patternp != *bufp) return FALSE;
1796         patternp++;
1797         bufp++;
1798     }
1799 }
1800
1801 void
1802 SendToPlayer(data, length)
1803      char *data;
1804      int length;
1805 {
1806     int error, outCount;
1807     outCount = OutputToProcess(NoProc, data, length, &error);
1808     if (outCount < length) {
1809         DisplayFatalError(_("Error writing to display"), error, 1);
1810     }
1811 }
1812
1813 void
1814 PackHolding(packed, holding)
1815      char packed[];
1816      char *holding;
1817 {
1818     char *p = holding;
1819     char *q = packed;
1820     int runlength = 0;
1821     int curr = 9999;
1822     do {
1823         if (*p == curr) {
1824             runlength++;
1825         } else {
1826             switch (runlength) {
1827               case 0:
1828                 break;
1829               case 1:
1830                 *q++ = curr;
1831                 break;
1832               case 2:
1833                 *q++ = curr;
1834                 *q++ = curr;
1835                 break;
1836               default:
1837                 sprintf(q, "%d", runlength);
1838                 while (*q) q++;
1839                 *q++ = curr;
1840                 break;
1841             }
1842             runlength = 1;
1843             curr = *p;
1844         }
1845     } while (*p++);
1846     *q = NULLCHAR;
1847 }
1848
1849 /* Telnet protocol requests from the front end */
1850 void
1851 TelnetRequest(ddww, option)
1852      unsigned char ddww, option;
1853 {
1854     unsigned char msg[3];
1855     int outCount, outError;
1856
1857     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1858
1859     if (appData.debugMode) {
1860         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1861         switch (ddww) {
1862           case TN_DO:
1863             ddwwStr = "DO";
1864             break;
1865           case TN_DONT:
1866             ddwwStr = "DONT";
1867             break;
1868           case TN_WILL:
1869             ddwwStr = "WILL";
1870             break;
1871           case TN_WONT:
1872             ddwwStr = "WONT";
1873             break;
1874           default:
1875             ddwwStr = buf1;
1876             sprintf(buf1, "%d", ddww);
1877             break;
1878         }
1879         switch (option) {
1880           case TN_ECHO:
1881             optionStr = "ECHO";
1882             break;
1883           default:
1884             optionStr = buf2;
1885             sprintf(buf2, "%d", option);
1886             break;
1887         }
1888         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1889     }
1890     msg[0] = TN_IAC;
1891     msg[1] = ddww;
1892     msg[2] = option;
1893     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1894     if (outCount < 3) {
1895         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1896     }
1897 }
1898
1899 void
1900 DoEcho()
1901 {
1902     if (!appData.icsActive) return;
1903     TelnetRequest(TN_DO, TN_ECHO);
1904 }
1905
1906 void
1907 DontEcho()
1908 {
1909     if (!appData.icsActive) return;
1910     TelnetRequest(TN_DONT, TN_ECHO);
1911 }
1912
1913 void
1914 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1915 {
1916     /* put the holdings sent to us by the server on the board holdings area */
1917     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1918     char p;
1919     ChessSquare piece;
1920
1921     if(gameInfo.holdingsWidth < 2)  return;
1922     if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1923         return; // prevent overwriting by pre-board holdings
1924
1925     if( (int)lowestPiece >= BlackPawn ) {
1926         holdingsColumn = 0;
1927         countsColumn = 1;
1928         holdingsStartRow = BOARD_HEIGHT-1;
1929         direction = -1;
1930     } else {
1931         holdingsColumn = BOARD_WIDTH-1;
1932         countsColumn = BOARD_WIDTH-2;
1933         holdingsStartRow = 0;
1934         direction = 1;
1935     }
1936
1937     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1938         board[i][holdingsColumn] = EmptySquare;
1939         board[i][countsColumn]   = (ChessSquare) 0;
1940     }
1941     while( (p=*holdings++) != NULLCHAR ) {
1942         piece = CharToPiece( ToUpper(p) );
1943         if(piece == EmptySquare) continue;
1944         /*j = (int) piece - (int) WhitePawn;*/
1945         j = PieceToNumber(piece);
1946         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1947         if(j < 0) continue;               /* should not happen */
1948         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1949         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1950         board[holdingsStartRow+j*direction][countsColumn]++;
1951     }
1952 }
1953
1954
1955 void
1956 VariantSwitch(Board board, VariantClass newVariant)
1957 {
1958    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1959    Board oldBoard;
1960
1961    startedFromPositionFile = FALSE;
1962    if(gameInfo.variant == newVariant) return;
1963
1964    /* [HGM] This routine is called each time an assignment is made to
1965     * gameInfo.variant during a game, to make sure the board sizes
1966     * are set to match the new variant. If that means adding or deleting
1967     * holdings, we shift the playing board accordingly
1968     * This kludge is needed because in ICS observe mode, we get boards
1969     * of an ongoing game without knowing the variant, and learn about the
1970     * latter only later. This can be because of the move list we requested,
1971     * in which case the game history is refilled from the beginning anyway,
1972     * but also when receiving holdings of a crazyhouse game. In the latter
1973     * case we want to add those holdings to the already received position.
1974     */
1975
1976    
1977    if (appData.debugMode) {
1978      fprintf(debugFP, "Switch board from %s to %s\n",
1979              VariantName(gameInfo.variant), VariantName(newVariant));
1980      setbuf(debugFP, NULL);
1981    }
1982    shuffleOpenings = 0;       /* [HGM] shuffle */
1983    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1984    switch(newVariant) 
1985      {
1986      case VariantShogi:
1987        newWidth = 9;  newHeight = 9;
1988        gameInfo.holdingsSize = 7;
1989      case VariantBughouse:
1990      case VariantCrazyhouse:
1991        newHoldingsWidth = 2; break;
1992      case VariantGreat:
1993        newWidth = 10;
1994      case VariantSuper:
1995        newHoldingsWidth = 2;
1996        gameInfo.holdingsSize = 8;
1997        break;
1998      case VariantGothic:
1999      case VariantCapablanca:
2000      case VariantCapaRandom:
2001        newWidth = 10;
2002      default:
2003        newHoldingsWidth = gameInfo.holdingsSize = 0;
2004      };
2005    
2006    if(newWidth  != gameInfo.boardWidth  ||
2007       newHeight != gameInfo.boardHeight ||
2008       newHoldingsWidth != gameInfo.holdingsWidth ) {
2009      
2010      /* shift position to new playing area, if needed */
2011      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2012        for(i=0; i<BOARD_HEIGHT; i++) 
2013          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2014            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2015              board[i][j];
2016        for(i=0; i<newHeight; i++) {
2017          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2018          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2019        }
2020      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2021        for(i=0; i<BOARD_HEIGHT; i++)
2022          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2023            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2024              board[i][j];
2025      }
2026      gameInfo.boardWidth  = newWidth;
2027      gameInfo.boardHeight = newHeight;
2028      gameInfo.holdingsWidth = newHoldingsWidth;
2029      gameInfo.variant = newVariant;
2030      InitDrawingSizes(-2, 0);
2031    } else gameInfo.variant = newVariant;
2032    CopyBoard(oldBoard, board);   // remember correctly formatted board
2033      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2034    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2035 }
2036
2037 static int loggedOn = FALSE;
2038
2039 /*-- Game start info cache: --*/
2040 int gs_gamenum;
2041 char gs_kind[MSG_SIZ];
2042 static char player1Name[128] = "";
2043 static char player2Name[128] = "";
2044 static char cont_seq[] = "\n\\   ";
2045 static int player1Rating = -1;
2046 static int player2Rating = -1;
2047 /*----------------------------*/
2048
2049 ColorClass curColor = ColorNormal;
2050 int suppressKibitz = 0;
2051
2052 void
2053 read_from_ics(isr, closure, data, count, error)
2054      InputSourceRef isr;
2055      VOIDSTAR closure;
2056      char *data;
2057      int count;
2058      int error;
2059 {
2060 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2061 #define STARTED_NONE 0
2062 #define STARTED_MOVES 1
2063 #define STARTED_BOARD 2
2064 #define STARTED_OBSERVE 3
2065 #define STARTED_HOLDINGS 4
2066 #define STARTED_CHATTER 5
2067 #define STARTED_COMMENT 6
2068 #define STARTED_MOVES_NOHIDE 7
2069     
2070     static int started = STARTED_NONE;
2071     static char parse[20000];
2072     static int parse_pos = 0;
2073     static char buf[BUF_SIZE + 1];
2074     static int firstTime = TRUE, intfSet = FALSE;
2075     static ColorClass prevColor = ColorNormal;
2076     static int savingComment = FALSE;
2077     static int cmatch = 0; // continuation sequence match
2078     char *bp;
2079     char str[500];
2080     int i, oldi;
2081     int buf_len;
2082     int next_out;
2083     int tkind;
2084     int backup;    /* [DM] For zippy color lines */
2085     char *p;
2086     char talker[MSG_SIZ]; // [HGM] chat
2087     int channel;
2088
2089     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2090
2091     if (appData.debugMode) {
2092       if (!error) {
2093         fprintf(debugFP, "<ICS: ");
2094         show_bytes(debugFP, data, count);
2095         fprintf(debugFP, "\n");
2096       }
2097     }
2098
2099     if (appData.debugMode) { int f = forwardMostMove;
2100         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2101                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2102     }
2103     if (count > 0) {
2104         /* If last read ended with a partial line that we couldn't parse,
2105            prepend it to the new read and try again. */
2106         if (leftover_len > 0) {
2107             for (i=0; i<leftover_len; i++)
2108               buf[i] = buf[leftover_start + i];
2109         }
2110
2111     /* copy new characters into the buffer */
2112     bp = buf + leftover_len;
2113     buf_len=leftover_len;
2114     for (i=0; i<count; i++)
2115     {
2116         // ignore these
2117         if (data[i] == '\r')
2118             continue;
2119
2120         // join lines split by ICS?
2121         if (!appData.noJoin)
2122         {
2123             /*
2124                 Joining just consists of finding matches against the
2125                 continuation sequence, and discarding that sequence
2126                 if found instead of copying it.  So, until a match
2127                 fails, there's nothing to do since it might be the
2128                 complete sequence, and thus, something we don't want
2129                 copied.
2130             */
2131             if (data[i] == cont_seq[cmatch])
2132             {
2133                 cmatch++;
2134                 if (cmatch == strlen(cont_seq))
2135                 {
2136                     cmatch = 0; // complete match.  just reset the counter
2137
2138                     /*
2139                         it's possible for the ICS to not include the space
2140                         at the end of the last word, making our [correct]
2141                         join operation fuse two separate words.  the server
2142                         does this when the space occurs at the width setting.
2143                     */
2144                     if (!buf_len || buf[buf_len-1] != ' ')
2145                     {
2146                         *bp++ = ' ';
2147                         buf_len++;
2148                     }
2149                 }
2150                 continue;
2151             }
2152             else if (cmatch)
2153             {
2154                 /*
2155                     match failed, so we have to copy what matched before
2156                     falling through and copying this character.  In reality,
2157                     this will only ever be just the newline character, but
2158                     it doesn't hurt to be precise.
2159                 */
2160                 strncpy(bp, cont_seq, cmatch);
2161                 bp += cmatch;
2162                 buf_len += cmatch;
2163                 cmatch = 0;
2164             }
2165         }
2166
2167         // copy this char
2168         *bp++ = data[i];
2169         buf_len++;
2170     }
2171
2172         buf[buf_len] = NULLCHAR;
2173 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2174         next_out = 0;
2175         leftover_start = 0;
2176         
2177         i = 0;
2178         while (i < buf_len) {
2179             /* Deal with part of the TELNET option negotiation
2180                protocol.  We refuse to do anything beyond the
2181                defaults, except that we allow the WILL ECHO option,
2182                which ICS uses to turn off password echoing when we are
2183                directly connected to it.  We reject this option
2184                if localLineEditing mode is on (always on in xboard)
2185                and we are talking to port 23, which might be a real
2186                telnet server that will try to keep WILL ECHO on permanently.
2187              */
2188             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2189                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2190                 unsigned char option;
2191                 oldi = i;
2192                 switch ((unsigned char) buf[++i]) {
2193                   case TN_WILL:
2194                     if (appData.debugMode)
2195                       fprintf(debugFP, "\n<WILL ");
2196                     switch (option = (unsigned char) buf[++i]) {
2197                       case TN_ECHO:
2198                         if (appData.debugMode)
2199                           fprintf(debugFP, "ECHO ");
2200                         /* Reply only if this is a change, according
2201                            to the protocol rules. */
2202                         if (remoteEchoOption) break;
2203                         if (appData.localLineEditing &&
2204                             atoi(appData.icsPort) == TN_PORT) {
2205                             TelnetRequest(TN_DONT, TN_ECHO);
2206                         } else {
2207                             EchoOff();
2208                             TelnetRequest(TN_DO, TN_ECHO);
2209                             remoteEchoOption = TRUE;
2210                         }
2211                         break;
2212                       default:
2213                         if (appData.debugMode)
2214                           fprintf(debugFP, "%d ", option);
2215                         /* Whatever this is, we don't want it. */
2216                         TelnetRequest(TN_DONT, option);
2217                         break;
2218                     }
2219                     break;
2220                   case TN_WONT:
2221                     if (appData.debugMode)
2222                       fprintf(debugFP, "\n<WONT ");
2223                     switch (option = (unsigned char) buf[++i]) {
2224                       case TN_ECHO:
2225                         if (appData.debugMode)
2226                           fprintf(debugFP, "ECHO ");
2227                         /* Reply only if this is a change, according
2228                            to the protocol rules. */
2229                         if (!remoteEchoOption) break;
2230                         EchoOn();
2231                         TelnetRequest(TN_DONT, TN_ECHO);
2232                         remoteEchoOption = FALSE;
2233                         break;
2234                       default:
2235                         if (appData.debugMode)
2236                           fprintf(debugFP, "%d ", (unsigned char) option);
2237                         /* Whatever this is, it must already be turned
2238                            off, because we never agree to turn on
2239                            anything non-default, so according to the
2240                            protocol rules, we don't reply. */
2241                         break;
2242                     }
2243                     break;
2244                   case TN_DO:
2245                     if (appData.debugMode)
2246                       fprintf(debugFP, "\n<DO ");
2247                     switch (option = (unsigned char) buf[++i]) {
2248                       default:
2249                         /* Whatever this is, we refuse to do it. */
2250                         if (appData.debugMode)
2251                           fprintf(debugFP, "%d ", option);
2252                         TelnetRequest(TN_WONT, option);
2253                         break;
2254                     }
2255                     break;
2256                   case TN_DONT:
2257                     if (appData.debugMode)
2258                       fprintf(debugFP, "\n<DONT ");
2259                     switch (option = (unsigned char) buf[++i]) {
2260                       default:
2261                         if (appData.debugMode)
2262                           fprintf(debugFP, "%d ", option);
2263                         /* Whatever this is, we are already not doing
2264                            it, because we never agree to do anything
2265                            non-default, so according to the protocol
2266                            rules, we don't reply. */
2267                         break;
2268                     }
2269                     break;
2270                   case TN_IAC:
2271                     if (appData.debugMode)
2272                       fprintf(debugFP, "\n<IAC ");
2273                     /* Doubled IAC; pass it through */
2274                     i--;
2275                     break;
2276                   default:
2277                     if (appData.debugMode)
2278                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2279                     /* Drop all other telnet commands on the floor */
2280                     break;
2281                 }
2282                 if (oldi > next_out)
2283                   SendToPlayer(&buf[next_out], oldi - next_out);
2284                 if (++i > next_out)
2285                   next_out = i;
2286                 continue;
2287             }
2288                 
2289             /* OK, this at least will *usually* work */
2290             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2291                 loggedOn = TRUE;
2292             }
2293             
2294             if (loggedOn && !intfSet) {
2295                 if (ics_type == ICS_ICC) {
2296                   sprintf(str,
2297                           "/set-quietly interface %s\n/set-quietly style 12\n",
2298                           programVersion);
2299                 } else if (ics_type == ICS_CHESSNET) {
2300                   sprintf(str, "/style 12\n");
2301                 } else {
2302                   strcpy(str, "alias $ @\n$set interface ");
2303                   strcat(str, programVersion);
2304                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2305 #ifdef WIN32
2306                   strcat(str, "$iset nohighlight 1\n");
2307 #endif
2308                   strcat(str, "$iset lock 1\n$style 12\n");
2309                 }
2310                 SendToICS(str);
2311                 NotifyFrontendLogin();
2312                 intfSet = TRUE;
2313             }
2314
2315             if (started == STARTED_COMMENT) {
2316                 /* Accumulate characters in comment */
2317                 parse[parse_pos++] = buf[i];
2318                 if (buf[i] == '\n') {
2319                     parse[parse_pos] = NULLCHAR;
2320                     if(chattingPartner>=0) {
2321                         char mess[MSG_SIZ];
2322                         sprintf(mess, "%s%s", talker, parse);
2323                         OutputChatMessage(chattingPartner, mess);
2324                         chattingPartner = -1;
2325                     } else
2326                     if(!suppressKibitz) // [HGM] kibitz
2327                         AppendComment(forwardMostMove, StripHighlight(parse));
2328                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2329                         int nrDigit = 0, nrAlph = 0, j;
2330                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2331                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2332                         parse[parse_pos] = NULLCHAR;
2333                         // try to be smart: if it does not look like search info, it should go to
2334                         // ICS interaction window after all, not to engine-output window.
2335                         for(j=0; j<parse_pos; j++) { // count letters and digits
2336                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2337                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2338                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2339                         }
2340                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2341                             int depth=0; float score;
2342                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2343                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2344                                 pvInfoList[forwardMostMove-1].depth = depth;
2345                                 pvInfoList[forwardMostMove-1].score = 100*score;
2346                             }
2347                             OutputKibitz(suppressKibitz, parse);
2348                             next_out = i+1; // [HGM] suppress printing in ICS window
2349                         } else {
2350                             char tmp[MSG_SIZ];
2351                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2352                             SendToPlayer(tmp, strlen(tmp));
2353                         }
2354                     }
2355                     started = STARTED_NONE;
2356                 } else {
2357                     /* Don't match patterns against characters in comment */
2358                     i++;
2359                     continue;
2360                 }
2361             }
2362             if (started == STARTED_CHATTER) {
2363                 if (buf[i] != '\n') {
2364                     /* Don't match patterns against characters in chatter */
2365                     i++;
2366                     continue;
2367                 }
2368                 started = STARTED_NONE;
2369             }
2370
2371             /* Kludge to deal with rcmd protocol */
2372             if (firstTime && looking_at(buf, &i, "\001*")) {
2373                 DisplayFatalError(&buf[1], 0, 1);
2374                 continue;
2375             } else {
2376                 firstTime = FALSE;
2377             }
2378
2379             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2380                 ics_type = ICS_ICC;
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, "freechess.org")) {
2387                 ics_type = ICS_FICS;
2388                 ics_prefix = "$";
2389                 if (appData.debugMode)
2390                   fprintf(debugFP, "ics_type %d\n", ics_type);
2391                 continue;
2392             }
2393             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2394                 ics_type = ICS_CHESSNET;
2395                 ics_prefix = "/";
2396                 if (appData.debugMode)
2397                   fprintf(debugFP, "ics_type %d\n", ics_type);
2398                 continue;
2399             }
2400
2401             if (!loggedOn &&
2402                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2403                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2404                  looking_at(buf, &i, "will be \"*\""))) {
2405               strcpy(ics_handle, star_match[0]);
2406               continue;
2407             }
2408
2409             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2410               char buf[MSG_SIZ];
2411               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2412               DisplayIcsInteractionTitle(buf);
2413               have_set_title = TRUE;
2414             }
2415
2416             /* skip finger notes */
2417             if (started == STARTED_NONE &&
2418                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2419                  (buf[i] == '1' && buf[i+1] == '0')) &&
2420                 buf[i+2] == ':' && buf[i+3] == ' ') {
2421               started = STARTED_CHATTER;
2422               i += 3;
2423               continue;
2424             }
2425
2426             /* skip formula vars */
2427             if (started == STARTED_NONE &&
2428                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2429               started = STARTED_CHATTER;
2430               i += 3;
2431               continue;
2432             }
2433
2434             oldi = i;
2435             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2436             if (appData.autoKibitz && started == STARTED_NONE && 
2437                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2438                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2439                 if(looking_at(buf, &i, "* kibitzes: ") &&
2440                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2441                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2442                         suppressKibitz = TRUE;
2443                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2444                                 && (gameMode == IcsPlayingWhite)) ||
2445                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2446                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2447                             started = STARTED_CHATTER; // own kibitz we simply discard
2448                         else {
2449                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2450                             parse_pos = 0; parse[0] = NULLCHAR;
2451                             savingComment = TRUE;
2452                             suppressKibitz = gameMode != IcsObserving ? 2 :
2453                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2454                         } 
2455                         continue;
2456                 } else
2457                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2458                     // suppress the acknowledgements of our own autoKibitz
2459                     char *p;
2460                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2461                     SendToPlayer(star_match[0], strlen(star_match[0]));
2462                     looking_at(buf, &i, "*% "); // eat prompt
2463                     next_out = i;
2464                 }
2465             } // [HGM] kibitz: end of patch
2466
2467 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2468
2469             // [HGM] chat: intercept tells by users for which we have an open chat window
2470             channel = -1;
2471             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2472                                            looking_at(buf, &i, "* whispers:") ||
2473                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2474                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2475                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2476                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2477                 int p;
2478                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2479                 chattingPartner = -1;
2480
2481                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2482                 for(p=0; p<MAX_CHAT; p++) {
2483                     if(channel == atoi(chatPartner[p])) {
2484                     talker[0] = '['; strcat(talker, "] ");
2485                     chattingPartner = p; break;
2486                     }
2487                 } else
2488                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2489                 for(p=0; p<MAX_CHAT; p++) {
2490                     if(!strcmp("WHISPER", chatPartner[p])) {
2491                         talker[0] = '['; strcat(talker, "] ");
2492                         chattingPartner = p; break;
2493                     }
2494                 }
2495                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2496                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2497                     talker[0] = 0;
2498                     chattingPartner = p; break;
2499                 }
2500                 if(chattingPartner<0) i = oldi; else {
2501                     started = STARTED_COMMENT;
2502                     parse_pos = 0; parse[0] = NULLCHAR;
2503                     savingComment = 3 + chattingPartner; // counts as TRUE
2504                     suppressKibitz = TRUE;
2505                 }
2506             } // [HGM] chat: end of patch
2507
2508             if (appData.zippyTalk || appData.zippyPlay) {
2509                 /* [DM] Backup address for color zippy lines */
2510                 backup = i;
2511 #if ZIPPY
2512        #ifdef WIN32
2513                if (loggedOn == TRUE)
2514                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2515                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2516        #else
2517                 if (ZippyControl(buf, &i) ||
2518                     ZippyConverse(buf, &i) ||
2519                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2520                       loggedOn = TRUE;
2521                       if (!appData.colorize) continue;
2522                 }
2523        #endif
2524 #endif
2525             } // [DM] 'else { ' deleted
2526                 if (
2527                     /* Regular tells and says */
2528                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2529                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2530                     looking_at(buf, &i, "* says: ") ||
2531                     /* Don't color "message" or "messages" output */
2532                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2533                     looking_at(buf, &i, "*. * at *:*: ") ||
2534                     looking_at(buf, &i, "--* (*:*): ") ||
2535                     /* Message notifications (same color as tells) */
2536                     looking_at(buf, &i, "* has left a message ") ||
2537                     looking_at(buf, &i, "* just sent you a message:\n") ||
2538                     /* Whispers and kibitzes */
2539                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2540                     looking_at(buf, &i, "* kibitzes: ") ||
2541                     /* Channel tells */
2542                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2543
2544                   if (tkind == 1 && strchr(star_match[0], ':')) {
2545                       /* Avoid "tells you:" spoofs in channels */
2546                      tkind = 3;
2547                   }
2548                   if (star_match[0][0] == NULLCHAR ||
2549                       strchr(star_match[0], ' ') ||
2550                       (tkind == 3 && strchr(star_match[1], ' '))) {
2551                     /* Reject bogus matches */
2552                     i = oldi;
2553                   } else {
2554                     if (appData.colorize) {
2555                       if (oldi > next_out) {
2556                         SendToPlayer(&buf[next_out], oldi - next_out);
2557                         next_out = oldi;
2558                       }
2559                       switch (tkind) {
2560                       case 1:
2561                         Colorize(ColorTell, FALSE);
2562                         curColor = ColorTell;
2563                         break;
2564                       case 2:
2565                         Colorize(ColorKibitz, FALSE);
2566                         curColor = ColorKibitz;
2567                         break;
2568                       case 3:
2569                         p = strrchr(star_match[1], '(');
2570                         if (p == NULL) {
2571                           p = star_match[1];
2572                         } else {
2573                           p++;
2574                         }
2575                         if (atoi(p) == 1) {
2576                           Colorize(ColorChannel1, FALSE);
2577                           curColor = ColorChannel1;
2578                         } else {
2579                           Colorize(ColorChannel, FALSE);
2580                           curColor = ColorChannel;
2581                         }
2582                         break;
2583                       case 5:
2584                         curColor = ColorNormal;
2585                         break;
2586                       }
2587                     }
2588                     if (started == STARTED_NONE && appData.autoComment &&
2589                         (gameMode == IcsObserving ||
2590                          gameMode == IcsPlayingWhite ||
2591                          gameMode == IcsPlayingBlack)) {
2592                       parse_pos = i - oldi;
2593                       memcpy(parse, &buf[oldi], parse_pos);
2594                       parse[parse_pos] = NULLCHAR;
2595                       started = STARTED_COMMENT;
2596                       savingComment = TRUE;
2597                     } else {
2598                       started = STARTED_CHATTER;
2599                       savingComment = FALSE;
2600                     }
2601                     loggedOn = TRUE;
2602                     continue;
2603                   }
2604                 }
2605
2606                 if (looking_at(buf, &i, "* s-shouts: ") ||
2607                     looking_at(buf, &i, "* c-shouts: ")) {
2608                     if (appData.colorize) {
2609                         if (oldi > next_out) {
2610                             SendToPlayer(&buf[next_out], oldi - next_out);
2611                             next_out = oldi;
2612                         }
2613                         Colorize(ColorSShout, FALSE);
2614                         curColor = ColorSShout;
2615                     }
2616                     loggedOn = TRUE;
2617                     started = STARTED_CHATTER;
2618                     continue;
2619                 }
2620
2621                 if (looking_at(buf, &i, "--->")) {
2622                     loggedOn = TRUE;
2623                     continue;
2624                 }
2625
2626                 if (looking_at(buf, &i, "* shouts: ") ||
2627                     looking_at(buf, &i, "--> ")) {
2628                     if (appData.colorize) {
2629                         if (oldi > next_out) {
2630                             SendToPlayer(&buf[next_out], oldi - next_out);
2631                             next_out = oldi;
2632                         }
2633                         Colorize(ColorShout, FALSE);
2634                         curColor = ColorShout;
2635                     }
2636                     loggedOn = TRUE;
2637                     started = STARTED_CHATTER;
2638                     continue;
2639                 }
2640
2641                 if (looking_at( buf, &i, "Challenge:")) {
2642                     if (appData.colorize) {
2643                         if (oldi > next_out) {
2644                             SendToPlayer(&buf[next_out], oldi - next_out);
2645                             next_out = oldi;
2646                         }
2647                         Colorize(ColorChallenge, FALSE);
2648                         curColor = ColorChallenge;
2649                     }
2650                     loggedOn = TRUE;
2651                     continue;
2652                 }
2653
2654                 if (looking_at(buf, &i, "* offers you") ||
2655                     looking_at(buf, &i, "* offers to be") ||
2656                     looking_at(buf, &i, "* would like to") ||
2657                     looking_at(buf, &i, "* requests to") ||
2658                     looking_at(buf, &i, "Your opponent offers") ||
2659                     looking_at(buf, &i, "Your opponent requests")) {
2660
2661                     if (appData.colorize) {
2662                         if (oldi > next_out) {
2663                             SendToPlayer(&buf[next_out], oldi - next_out);
2664                             next_out = oldi;
2665                         }
2666                         Colorize(ColorRequest, FALSE);
2667                         curColor = ColorRequest;
2668                     }
2669                     continue;
2670                 }
2671
2672                 if (looking_at(buf, &i, "* (*) seeking")) {
2673                     if (appData.colorize) {
2674                         if (oldi > next_out) {
2675                             SendToPlayer(&buf[next_out], oldi - next_out);
2676                             next_out = oldi;
2677                         }
2678                         Colorize(ColorSeek, FALSE);
2679                         curColor = ColorSeek;
2680                     }
2681                     continue;
2682             }
2683
2684             if (looking_at(buf, &i, "\\   ")) {
2685                 if (prevColor != ColorNormal) {
2686                     if (oldi > next_out) {
2687                         SendToPlayer(&buf[next_out], oldi - next_out);
2688                         next_out = oldi;
2689                     }
2690                     Colorize(prevColor, TRUE);
2691                     curColor = prevColor;
2692                 }
2693                 if (savingComment) {
2694                     parse_pos = i - oldi;
2695                     memcpy(parse, &buf[oldi], parse_pos);
2696                     parse[parse_pos] = NULLCHAR;
2697                     started = STARTED_COMMENT;
2698                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2699                         chattingPartner = savingComment - 3; // kludge to remember the box
2700                 } else {
2701                     started = STARTED_CHATTER;
2702                 }
2703                 continue;
2704             }
2705
2706             if (looking_at(buf, &i, "Black Strength :") ||
2707                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2708                 looking_at(buf, &i, "<10>") ||
2709                 looking_at(buf, &i, "#@#")) {
2710                 /* Wrong board style */
2711                 loggedOn = TRUE;
2712                 SendToICS(ics_prefix);
2713                 SendToICS("set style 12\n");
2714                 SendToICS(ics_prefix);
2715                 SendToICS("refresh\n");
2716                 continue;
2717             }
2718             
2719             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2720                 ICSInitScript();
2721                 have_sent_ICS_logon = 1;
2722                 continue;
2723             }
2724               
2725             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2726                 (looking_at(buf, &i, "\n<12> ") ||
2727                  looking_at(buf, &i, "<12> "))) {
2728                 loggedOn = TRUE;
2729                 if (oldi > next_out) {
2730                     SendToPlayer(&buf[next_out], oldi - next_out);
2731                 }
2732                 next_out = i;
2733                 started = STARTED_BOARD;
2734                 parse_pos = 0;
2735                 continue;
2736             }
2737
2738             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2739                 looking_at(buf, &i, "<b1> ")) {
2740                 if (oldi > next_out) {
2741                     SendToPlayer(&buf[next_out], oldi - next_out);
2742                 }
2743                 next_out = i;
2744                 started = STARTED_HOLDINGS;
2745                 parse_pos = 0;
2746                 continue;
2747             }
2748
2749             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2750                 loggedOn = TRUE;
2751                 /* Header for a move list -- first line */
2752
2753                 switch (ics_getting_history) {
2754                   case H_FALSE:
2755                     switch (gameMode) {
2756                       case IcsIdle:
2757                       case BeginningOfGame:
2758                         /* User typed "moves" or "oldmoves" while we
2759                            were idle.  Pretend we asked for these
2760                            moves and soak them up so user can step
2761                            through them and/or save them.
2762                            */
2763                         Reset(FALSE, TRUE);
2764                         gameMode = IcsObserving;
2765                         ModeHighlight();
2766                         ics_gamenum = -1;
2767                         ics_getting_history = H_GOT_UNREQ_HEADER;
2768                         break;
2769                       case EditGame: /*?*/
2770                       case EditPosition: /*?*/
2771                         /* Should above feature work in these modes too? */
2772                         /* For now it doesn't */
2773                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2774                         break;
2775                       default:
2776                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2777                         break;
2778                     }
2779                     break;
2780                   case H_REQUESTED:
2781                     /* Is this the right one? */
2782                     if (gameInfo.white && gameInfo.black &&
2783                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2784                         strcmp(gameInfo.black, star_match[2]) == 0) {
2785                         /* All is well */
2786                         ics_getting_history = H_GOT_REQ_HEADER;
2787                     }
2788                     break;
2789                   case H_GOT_REQ_HEADER:
2790                   case H_GOT_UNREQ_HEADER:
2791                   case H_GOT_UNWANTED_HEADER:
2792                   case H_GETTING_MOVES:
2793                     /* Should not happen */
2794                     DisplayError(_("Error gathering move list: two headers"), 0);
2795                     ics_getting_history = H_FALSE;
2796                     break;
2797                 }
2798
2799                 /* Save player ratings into gameInfo if needed */
2800                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2801                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2802                     (gameInfo.whiteRating == -1 ||
2803                      gameInfo.blackRating == -1)) {
2804
2805                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2806                     gameInfo.blackRating = string_to_rating(star_match[3]);
2807                     if (appData.debugMode)
2808                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2809                               gameInfo.whiteRating, gameInfo.blackRating);
2810                 }
2811                 continue;
2812             }
2813
2814             if (looking_at(buf, &i,
2815               "* * match, initial time: * minute*, increment: * second")) {
2816                 /* Header for a move list -- second line */
2817                 /* Initial board will follow if this is a wild game */
2818                 if (gameInfo.event != NULL) free(gameInfo.event);
2819                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2820                 gameInfo.event = StrSave(str);
2821                 /* [HGM] we switched variant. Translate boards if needed. */
2822                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2823                 continue;
2824             }
2825
2826             if (looking_at(buf, &i, "Move  ")) {
2827                 /* Beginning of a move list */
2828                 switch (ics_getting_history) {
2829                   case H_FALSE:
2830                     /* Normally should not happen */
2831                     /* Maybe user hit reset while we were parsing */
2832                     break;
2833                   case H_REQUESTED:
2834                     /* Happens if we are ignoring a move list that is not
2835                      * the one we just requested.  Common if the user
2836                      * tries to observe two games without turning off
2837                      * getMoveList */
2838                     break;
2839                   case H_GETTING_MOVES:
2840                     /* Should not happen */
2841                     DisplayError(_("Error gathering move list: nested"), 0);
2842                     ics_getting_history = H_FALSE;
2843                     break;
2844                   case H_GOT_REQ_HEADER:
2845                     ics_getting_history = H_GETTING_MOVES;
2846                     started = STARTED_MOVES;
2847                     parse_pos = 0;
2848                     if (oldi > next_out) {
2849                         SendToPlayer(&buf[next_out], oldi - next_out);
2850                     }
2851                     break;
2852                   case H_GOT_UNREQ_HEADER:
2853                     ics_getting_history = H_GETTING_MOVES;
2854                     started = STARTED_MOVES_NOHIDE;
2855                     parse_pos = 0;
2856                     break;
2857                   case H_GOT_UNWANTED_HEADER:
2858                     ics_getting_history = H_FALSE;
2859                     break;
2860                 }
2861                 continue;
2862             }                           
2863             
2864             if (looking_at(buf, &i, "% ") ||
2865                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2866                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2867                 if(suppressKibitz) next_out = i;
2868                 savingComment = FALSE;
2869                 suppressKibitz = 0;
2870                 switch (started) {
2871                   case STARTED_MOVES:
2872                   case STARTED_MOVES_NOHIDE:
2873                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2874                     parse[parse_pos + i - oldi] = NULLCHAR;
2875                     ParseGameHistory(parse);
2876 #if ZIPPY
2877                     if (appData.zippyPlay && first.initDone) {
2878                         FeedMovesToProgram(&first, forwardMostMove);
2879                         if (gameMode == IcsPlayingWhite) {
2880                             if (WhiteOnMove(forwardMostMove)) {
2881                                 if (first.sendTime) {
2882                                   if (first.useColors) {
2883                                     SendToProgram("black\n", &first); 
2884                                   }
2885                                   SendTimeRemaining(&first, TRUE);
2886                                 }
2887                                 if (first.useColors) {
2888                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2889                                 }
2890                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2891                                 first.maybeThinking = TRUE;
2892                             } else {
2893                                 if (first.usePlayother) {
2894                                   if (first.sendTime) {
2895                                     SendTimeRemaining(&first, TRUE);
2896                                   }
2897                                   SendToProgram("playother\n", &first);
2898                                   firstMove = FALSE;
2899                                 } else {
2900                                   firstMove = TRUE;
2901                                 }
2902                             }
2903                         } else if (gameMode == IcsPlayingBlack) {
2904                             if (!WhiteOnMove(forwardMostMove)) {
2905                                 if (first.sendTime) {
2906                                   if (first.useColors) {
2907                                     SendToProgram("white\n", &first);
2908                                   }
2909                                   SendTimeRemaining(&first, FALSE);
2910                                 }
2911                                 if (first.useColors) {
2912                                   SendToProgram("black\n", &first);
2913                                 }
2914                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2915                                 first.maybeThinking = TRUE;
2916                             } else {
2917                                 if (first.usePlayother) {
2918                                   if (first.sendTime) {
2919                                     SendTimeRemaining(&first, FALSE);
2920                                   }
2921                                   SendToProgram("playother\n", &first);
2922                                   firstMove = FALSE;
2923                                 } else {
2924                                   firstMove = TRUE;
2925                                 }
2926                             }
2927                         }                       
2928                     }
2929 #endif
2930                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2931                         /* Moves came from oldmoves or moves command
2932                            while we weren't doing anything else.
2933                            */
2934                         currentMove = forwardMostMove;
2935                         ClearHighlights();/*!!could figure this out*/
2936                         flipView = appData.flipView;
2937                         DrawPosition(TRUE, boards[currentMove]);
2938                         DisplayBothClocks();
2939                         sprintf(str, "%s vs. %s",
2940                                 gameInfo.white, gameInfo.black);
2941                         DisplayTitle(str);
2942                         gameMode = IcsIdle;
2943                     } else {
2944                         /* Moves were history of an active game */
2945                         if (gameInfo.resultDetails != NULL) {
2946                             free(gameInfo.resultDetails);
2947                             gameInfo.resultDetails = NULL;
2948                         }
2949                     }
2950                     HistorySet(parseList, backwardMostMove,
2951                                forwardMostMove, currentMove-1);
2952                     DisplayMove(currentMove - 1);
2953                     if (started == STARTED_MOVES) next_out = i;
2954                     started = STARTED_NONE;
2955                     ics_getting_history = H_FALSE;
2956                     break;
2957
2958                   case STARTED_OBSERVE:
2959                     started = STARTED_NONE;
2960                     SendToICS(ics_prefix);
2961                     SendToICS("refresh\n");
2962                     break;
2963
2964                   default:
2965                     break;
2966                 }
2967                 if(bookHit) { // [HGM] book: simulate book reply
2968                     static char bookMove[MSG_SIZ]; // a bit generous?
2969
2970                     programStats.nodes = programStats.depth = programStats.time = 
2971                     programStats.score = programStats.got_only_move = 0;
2972                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2973
2974                     strcpy(bookMove, "move ");
2975                     strcat(bookMove, bookHit);
2976                     HandleMachineMove(bookMove, &first);
2977                 }
2978                 continue;
2979             }
2980             
2981             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2982                  started == STARTED_HOLDINGS ||
2983                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2984                 /* Accumulate characters in move list or board */
2985                 parse[parse_pos++] = buf[i];
2986             }
2987             
2988             /* Start of game messages.  Mostly we detect start of game
2989                when the first board image arrives.  On some versions
2990                of the ICS, though, we need to do a "refresh" after starting
2991                to observe in order to get the current board right away. */
2992             if (looking_at(buf, &i, "Adding game * to observation list")) {
2993                 started = STARTED_OBSERVE;
2994                 continue;
2995             }
2996
2997             /* Handle auto-observe */
2998             if (appData.autoObserve &&
2999                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3000                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3001                 char *player;
3002                 /* Choose the player that was highlighted, if any. */
3003                 if (star_match[0][0] == '\033' ||
3004                     star_match[1][0] != '\033') {
3005                     player = star_match[0];
3006                 } else {
3007                     player = star_match[2];
3008                 }
3009                 sprintf(str, "%sobserve %s\n",
3010                         ics_prefix, StripHighlightAndTitle(player));
3011                 SendToICS(str);
3012
3013                 /* Save ratings from notify string */
3014                 strcpy(player1Name, star_match[0]);
3015                 player1Rating = string_to_rating(star_match[1]);
3016                 strcpy(player2Name, star_match[2]);
3017                 player2Rating = string_to_rating(star_match[3]);
3018
3019                 if (appData.debugMode)
3020                   fprintf(debugFP, 
3021                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3022                           player1Name, player1Rating,
3023                           player2Name, player2Rating);
3024
3025                 continue;
3026             }
3027
3028             /* Deal with automatic examine mode after a game,
3029                and with IcsObserving -> IcsExamining transition */
3030             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3031                 looking_at(buf, &i, "has made you an examiner of game *")) {
3032
3033                 int gamenum = atoi(star_match[0]);
3034                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3035                     gamenum == ics_gamenum) {
3036                     /* We were already playing or observing this game;
3037                        no need to refetch history */
3038                     gameMode = IcsExamining;
3039                     if (pausing) {
3040                         pauseExamForwardMostMove = forwardMostMove;
3041                     } else if (currentMove < forwardMostMove) {
3042                         ForwardInner(forwardMostMove);
3043                     }
3044                 } else {
3045                     /* I don't think this case really can happen */
3046                     SendToICS(ics_prefix);
3047                     SendToICS("refresh\n");
3048                 }
3049                 continue;
3050             }    
3051             
3052             /* Error messages */
3053 //          if (ics_user_moved) {
3054             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3055                 if (looking_at(buf, &i, "Illegal move") ||
3056                     looking_at(buf, &i, "Not a legal move") ||
3057                     looking_at(buf, &i, "Your king is in check") ||
3058                     looking_at(buf, &i, "It isn't your turn") ||
3059                     looking_at(buf, &i, "It is not your move")) {
3060                     /* Illegal move */
3061                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3062                         currentMove = forwardMostMove-1;
3063                         DisplayMove(currentMove - 1); /* before DMError */
3064                         DrawPosition(FALSE, boards[currentMove]);
3065                         SwitchClocks(forwardMostMove-1); // [HGM] race
3066                         DisplayBothClocks();
3067                     }
3068                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3069                     ics_user_moved = 0;
3070                     continue;
3071                 }
3072             }
3073
3074             if (looking_at(buf, &i, "still have time") ||
3075                 looking_at(buf, &i, "not out of time") ||
3076                 looking_at(buf, &i, "either player is out of time") ||
3077                 looking_at(buf, &i, "has timeseal; checking")) {
3078                 /* We must have called his flag a little too soon */
3079                 whiteFlag = blackFlag = FALSE;
3080                 continue;
3081             }
3082
3083             if (looking_at(buf, &i, "added * seconds to") ||
3084                 looking_at(buf, &i, "seconds were added to")) {
3085                 /* Update the clocks */
3086                 SendToICS(ics_prefix);
3087                 SendToICS("refresh\n");
3088                 continue;
3089             }
3090
3091             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3092                 ics_clock_paused = TRUE;
3093                 StopClocks();
3094                 continue;
3095             }
3096
3097             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3098                 ics_clock_paused = FALSE;
3099                 StartClocks();
3100                 continue;
3101             }
3102
3103             /* Grab player ratings from the Creating: message.
3104                Note we have to check for the special case when
3105                the ICS inserts things like [white] or [black]. */
3106             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3107                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3108                 /* star_matches:
3109                    0    player 1 name (not necessarily white)
3110                    1    player 1 rating
3111                    2    empty, white, or black (IGNORED)
3112                    3    player 2 name (not necessarily black)
3113                    4    player 2 rating
3114                    
3115                    The names/ratings are sorted out when the game
3116                    actually starts (below).
3117                 */
3118                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3119                 player1Rating = string_to_rating(star_match[1]);
3120                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3121                 player2Rating = string_to_rating(star_match[4]);
3122
3123                 if (appData.debugMode)
3124                   fprintf(debugFP, 
3125                           "Ratings from 'Creating:' %s %d, %s %d\n",
3126                           player1Name, player1Rating,
3127                           player2Name, player2Rating);
3128
3129                 continue;
3130             }
3131             
3132             /* Improved generic start/end-of-game messages */
3133             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3134                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3135                 /* If tkind == 0: */
3136                 /* star_match[0] is the game number */
3137                 /*           [1] is the white player's name */
3138                 /*           [2] is the black player's name */
3139                 /* For end-of-game: */
3140                 /*           [3] is the reason for the game end */
3141                 /*           [4] is a PGN end game-token, preceded by " " */
3142                 /* For start-of-game: */
3143                 /*           [3] begins with "Creating" or "Continuing" */
3144                 /*           [4] is " *" or empty (don't care). */
3145                 int gamenum = atoi(star_match[0]);
3146                 char *whitename, *blackname, *why, *endtoken;
3147                 ChessMove endtype = (ChessMove) 0;
3148
3149                 if (tkind == 0) {
3150                   whitename = star_match[1];
3151                   blackname = star_match[2];
3152                   why = star_match[3];
3153                   endtoken = star_match[4];
3154                 } else {
3155                   whitename = star_match[1];
3156                   blackname = star_match[3];
3157                   why = star_match[5];
3158                   endtoken = star_match[6];
3159                 }
3160
3161                 /* Game start messages */
3162                 if (strncmp(why, "Creating ", 9) == 0 ||
3163                     strncmp(why, "Continuing ", 11) == 0) {
3164                     gs_gamenum = gamenum;
3165                     strcpy(gs_kind, strchr(why, ' ') + 1);
3166                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3167 #if ZIPPY
3168                     if (appData.zippyPlay) {
3169                         ZippyGameStart(whitename, blackname);
3170                     }
3171 #endif /*ZIPPY*/
3172                     continue;
3173                 }
3174
3175                 /* Game end messages */
3176                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3177                     ics_gamenum != gamenum) {
3178                     continue;
3179                 }
3180                 while (endtoken[0] == ' ') endtoken++;
3181                 switch (endtoken[0]) {
3182                   case '*':
3183                   default:
3184                     endtype = GameUnfinished;
3185                     break;
3186                   case '0':
3187                     endtype = BlackWins;
3188                     break;
3189                   case '1':
3190                     if (endtoken[1] == '/')
3191                       endtype = GameIsDrawn;
3192                     else
3193                       endtype = WhiteWins;
3194                     break;
3195                 }
3196                 GameEnds(endtype, why, GE_ICS);
3197 #if ZIPPY
3198                 if (appData.zippyPlay && first.initDone) {
3199                     ZippyGameEnd(endtype, why);
3200                     if (first.pr == NULL) {
3201                       /* Start the next process early so that we'll
3202                          be ready for the next challenge */
3203                       StartChessProgram(&first);
3204                     }
3205                     /* Send "new" early, in case this command takes
3206                        a long time to finish, so that we'll be ready
3207                        for the next challenge. */
3208                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3209                     Reset(TRUE, TRUE);
3210                 }
3211 #endif /*ZIPPY*/
3212                 continue;
3213             }
3214
3215             if (looking_at(buf, &i, "Removing game * from observation") ||
3216                 looking_at(buf, &i, "no longer observing game *") ||
3217                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3218                 if (gameMode == IcsObserving &&
3219                     atoi(star_match[0]) == ics_gamenum)
3220                   {
3221                       /* icsEngineAnalyze */
3222                       if (appData.icsEngineAnalyze) {
3223                             ExitAnalyzeMode();
3224                             ModeHighlight();
3225                       }
3226                       StopClocks();
3227                       gameMode = IcsIdle;
3228                       ics_gamenum = -1;
3229                       ics_user_moved = FALSE;
3230                   }
3231                 continue;
3232             }
3233
3234             if (looking_at(buf, &i, "no longer examining game *")) {
3235                 if (gameMode == IcsExamining &&
3236                     atoi(star_match[0]) == ics_gamenum)
3237                   {
3238                       gameMode = IcsIdle;
3239                       ics_gamenum = -1;
3240                       ics_user_moved = FALSE;
3241                   }
3242                 continue;
3243             }
3244
3245             /* Advance leftover_start past any newlines we find,
3246                so only partial lines can get reparsed */
3247             if (looking_at(buf, &i, "\n")) {
3248                 prevColor = curColor;
3249                 if (curColor != ColorNormal) {
3250                     if (oldi > next_out) {
3251                         SendToPlayer(&buf[next_out], oldi - next_out);
3252                         next_out = oldi;
3253                     }
3254                     Colorize(ColorNormal, FALSE);
3255                     curColor = ColorNormal;
3256                 }
3257                 if (started == STARTED_BOARD) {
3258                     started = STARTED_NONE;
3259                     parse[parse_pos] = NULLCHAR;
3260                     ParseBoard12(parse);
3261                     ics_user_moved = 0;
3262
3263                     /* Send premove here */
3264                     if (appData.premove) {
3265                       char str[MSG_SIZ];
3266                       if (currentMove == 0 &&
3267                           gameMode == IcsPlayingWhite &&
3268                           appData.premoveWhite) {
3269                         sprintf(str, "%s\n", appData.premoveWhiteText);
3270                         if (appData.debugMode)
3271                           fprintf(debugFP, "Sending premove:\n");
3272                         SendToICS(str);
3273                       } else if (currentMove == 1 &&
3274                                  gameMode == IcsPlayingBlack &&
3275                                  appData.premoveBlack) {
3276                         sprintf(str, "%s\n", appData.premoveBlackText);
3277                         if (appData.debugMode)
3278                           fprintf(debugFP, "Sending premove:\n");
3279                         SendToICS(str);
3280                       } else if (gotPremove) {
3281                         gotPremove = 0;
3282                         ClearPremoveHighlights();
3283                         if (appData.debugMode)
3284                           fprintf(debugFP, "Sending premove:\n");
3285                           UserMoveEvent(premoveFromX, premoveFromY, 
3286                                         premoveToX, premoveToY, 
3287                                         premovePromoChar);
3288                       }
3289                     }
3290
3291                     /* Usually suppress following prompt */
3292                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3293                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3294                         if (looking_at(buf, &i, "*% ")) {
3295                             savingComment = FALSE;
3296                             suppressKibitz = 0;
3297                         }
3298                     }
3299                     next_out = i;
3300                 } else if (started == STARTED_HOLDINGS) {
3301                     int gamenum;
3302                     char new_piece[MSG_SIZ];
3303                     started = STARTED_NONE;
3304                     parse[parse_pos] = NULLCHAR;
3305                     if (appData.debugMode)
3306                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3307                                                         parse, currentMove);
3308                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3309                         gamenum == ics_gamenum) {
3310                         if (gameInfo.variant == VariantNormal) {
3311                           /* [HGM] We seem to switch variant during a game!
3312                            * Presumably no holdings were displayed, so we have
3313                            * to move the position two files to the right to
3314                            * create room for them!
3315                            */
3316                           VariantClass newVariant;
3317                           switch(gameInfo.boardWidth) { // base guess on board width
3318                                 case 9:  newVariant = VariantShogi; break;
3319                                 case 10: newVariant = VariantGreat; break;
3320                                 default: newVariant = VariantCrazyhouse; break;
3321                           }
3322                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3323                           /* Get a move list just to see the header, which
3324                              will tell us whether this is really bug or zh */
3325                           if (ics_getting_history == H_FALSE) {
3326                             ics_getting_history = H_REQUESTED;
3327                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3328                             SendToICS(str);
3329                           }
3330                         }
3331                         new_piece[0] = NULLCHAR;
3332                         sscanf(parse, "game %d white [%s black [%s <- %s",
3333                                &gamenum, white_holding, black_holding,
3334                                new_piece);
3335                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3336                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3337                         /* [HGM] copy holdings to board holdings area */
3338                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3339                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3340                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3341 #if ZIPPY
3342                         if (appData.zippyPlay && first.initDone) {
3343                             ZippyHoldings(white_holding, black_holding,
3344                                           new_piece);
3345                         }
3346 #endif /*ZIPPY*/
3347                         if (tinyLayout || smallLayout) {
3348                             char wh[16], bh[16];
3349                             PackHolding(wh, white_holding);
3350                             PackHolding(bh, black_holding);
3351                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3352                                     gameInfo.white, gameInfo.black);
3353                         } else {
3354                             sprintf(str, "%s [%s] vs. %s [%s]",
3355                                     gameInfo.white, white_holding,
3356                                     gameInfo.black, black_holding);
3357                         }
3358
3359                         DrawPosition(FALSE, boards[currentMove]);
3360                         DisplayTitle(str);
3361                     }
3362                     /* Suppress following prompt */
3363                     if (looking_at(buf, &i, "*% ")) {
3364                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3365                         savingComment = FALSE;
3366                         suppressKibitz = 0;
3367                     }
3368                     next_out = i;
3369                 }
3370                 continue;
3371             }
3372
3373             i++;                /* skip unparsed character and loop back */
3374         }
3375         
3376         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3377 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3378 //          SendToPlayer(&buf[next_out], i - next_out);
3379             started != STARTED_HOLDINGS && leftover_start > next_out) {
3380             SendToPlayer(&buf[next_out], leftover_start - next_out);
3381             next_out = i;
3382         }
3383         
3384         leftover_len = buf_len - leftover_start;
3385         /* if buffer ends with something we couldn't parse,
3386            reparse it after appending the next read */
3387         
3388     } else if (count == 0) {
3389         RemoveInputSource(isr);
3390         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3391     } else {
3392         DisplayFatalError(_("Error reading from ICS"), error, 1);
3393     }
3394 }
3395
3396
3397 /* Board style 12 looks like this:
3398    
3399    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3400    
3401  * The "<12> " is stripped before it gets to this routine.  The two
3402  * trailing 0's (flip state and clock ticking) are later addition, and
3403  * some chess servers may not have them, or may have only the first.
3404  * Additional trailing fields may be added in the future.  
3405  */
3406
3407 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3408
3409 #define RELATION_OBSERVING_PLAYED    0
3410 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3411 #define RELATION_PLAYING_MYMOVE      1
3412 #define RELATION_PLAYING_NOTMYMOVE  -1
3413 #define RELATION_EXAMINING           2
3414 #define RELATION_ISOLATED_BOARD     -3
3415 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3416
3417 void
3418 ParseBoard12(string)
3419      char *string;
3420
3421     GameMode newGameMode;
3422     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3423     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3424     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3425     char to_play, board_chars[200];
3426     char move_str[500], str[500], elapsed_time[500];
3427     char black[32], white[32];
3428     Board board;
3429     int prevMove = currentMove;
3430     int ticking = 2;
3431     ChessMove moveType;
3432     int fromX, fromY, toX, toY;
3433     char promoChar;
3434     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3435     char *bookHit = NULL; // [HGM] book
3436     Boolean weird = FALSE, reqFlag = FALSE;
3437
3438     fromX = fromY = toX = toY = -1;
3439     
3440     newGame = FALSE;
3441
3442     if (appData.debugMode)
3443       fprintf(debugFP, _("Parsing board: %s\n"), string);
3444
3445     move_str[0] = NULLCHAR;
3446     elapsed_time[0] = NULLCHAR;
3447     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3448         int  i = 0, j;
3449         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3450             if(string[i] == ' ') { ranks++; files = 0; }
3451             else files++;
3452             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3453             i++;
3454         }
3455         for(j = 0; j <i; j++) board_chars[j] = string[j];
3456         board_chars[i] = '\0';
3457         string += i + 1;
3458     }
3459     n = sscanf(string, PATTERN, &to_play, &double_push,
3460                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3461                &gamenum, white, black, &relation, &basetime, &increment,
3462                &white_stren, &black_stren, &white_time, &black_time,
3463                &moveNum, str, elapsed_time, move_str, &ics_flip,
3464                &ticking);
3465
3466     if (n < 21) {
3467         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3468         DisplayError(str, 0);
3469         return;
3470     }
3471
3472     /* Convert the move number to internal form */
3473     moveNum = (moveNum - 1) * 2;
3474     if (to_play == 'B') moveNum++;
3475     if (moveNum >= MAX_MOVES) {
3476       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3477                         0, 1);
3478       return;
3479     }
3480     
3481     switch (relation) {
3482       case RELATION_OBSERVING_PLAYED:
3483       case RELATION_OBSERVING_STATIC:
3484         if (gamenum == -1) {
3485             /* Old ICC buglet */
3486             relation = RELATION_OBSERVING_STATIC;
3487         }
3488         newGameMode = IcsObserving;
3489         break;
3490       case RELATION_PLAYING_MYMOVE:
3491       case RELATION_PLAYING_NOTMYMOVE:
3492         newGameMode =
3493           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3494             IcsPlayingWhite : IcsPlayingBlack;
3495         break;
3496       case RELATION_EXAMINING:
3497         newGameMode = IcsExamining;
3498         break;
3499       case RELATION_ISOLATED_BOARD:
3500       default:
3501         /* Just display this board.  If user was doing something else,
3502            we will forget about it until the next board comes. */ 
3503         newGameMode = IcsIdle;
3504         break;
3505       case RELATION_STARTING_POSITION:
3506         newGameMode = gameMode;
3507         break;
3508     }
3509     
3510     /* Modify behavior for initial board display on move listing
3511        of wild games.
3512        */
3513     switch (ics_getting_history) {
3514       case H_FALSE:
3515       case H_REQUESTED:
3516         break;
3517       case H_GOT_REQ_HEADER:
3518       case H_GOT_UNREQ_HEADER:
3519         /* This is the initial position of the current game */
3520         gamenum = ics_gamenum;
3521         moveNum = 0;            /* old ICS bug workaround */
3522         if (to_play == 'B') {
3523           startedFromSetupPosition = TRUE;
3524           blackPlaysFirst = TRUE;
3525           moveNum = 1;
3526           if (forwardMostMove == 0) forwardMostMove = 1;
3527           if (backwardMostMove == 0) backwardMostMove = 1;
3528           if (currentMove == 0) currentMove = 1;
3529         }
3530         newGameMode = gameMode;
3531         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3532         break;
3533       case H_GOT_UNWANTED_HEADER:
3534         /* This is an initial board that we don't want */
3535         return;
3536       case H_GETTING_MOVES:
3537         /* Should not happen */
3538         DisplayError(_("Error gathering move list: extra board"), 0);
3539         ics_getting_history = H_FALSE;
3540         return;
3541     }
3542
3543    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3544                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3545      /* [HGM] We seem to have switched variant unexpectedly
3546       * Try to guess new variant from board size
3547       */
3548           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3549           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3550           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3551           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3552           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3553           if(!weird) newVariant = VariantNormal;
3554           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3555           /* Get a move list just to see the header, which
3556              will tell us whether this is really bug or zh */
3557           if (ics_getting_history == H_FALSE) {
3558             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3559             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3560             SendToICS(str);
3561           }
3562     }
3563     
3564     /* Take action if this is the first board of a new game, or of a
3565        different game than is currently being displayed.  */
3566     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3567         relation == RELATION_ISOLATED_BOARD) {
3568         
3569         /* Forget the old game and get the history (if any) of the new one */
3570         if (gameMode != BeginningOfGame) {
3571           Reset(TRUE, TRUE);
3572         }
3573         newGame = TRUE;
3574         if (appData.autoRaiseBoard) BoardToTop();
3575         prevMove = -3;
3576         if (gamenum == -1) {
3577             newGameMode = IcsIdle;
3578         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3579                    appData.getMoveList && !reqFlag) {
3580             /* Need to get game history */
3581             ics_getting_history = H_REQUESTED;
3582             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3583             SendToICS(str);
3584         }
3585         
3586         /* Initially flip the board to have black on the bottom if playing
3587            black or if the ICS flip flag is set, but let the user change
3588            it with the Flip View button. */
3589         flipView = appData.autoFlipView ? 
3590           (newGameMode == IcsPlayingBlack) || ics_flip :
3591           appData.flipView;
3592         
3593         /* Done with values from previous mode; copy in new ones */
3594         gameMode = newGameMode;
3595         ModeHighlight();
3596         ics_gamenum = gamenum;
3597         if (gamenum == gs_gamenum) {
3598             int klen = strlen(gs_kind);
3599             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3600             sprintf(str, "ICS %s", gs_kind);
3601             gameInfo.event = StrSave(str);
3602         } else {
3603             gameInfo.event = StrSave("ICS game");
3604         }
3605         gameInfo.site = StrSave(appData.icsHost);
3606         gameInfo.date = PGNDate();
3607         gameInfo.round = StrSave("-");
3608         gameInfo.white = StrSave(white);
3609         gameInfo.black = StrSave(black);
3610         timeControl = basetime * 60 * 1000;
3611         timeControl_2 = 0;
3612         timeIncrement = increment * 1000;
3613         movesPerSession = 0;
3614         gameInfo.timeControl = TimeControlTagValue();
3615         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3616   if (appData.debugMode) {
3617     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3618     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3619     setbuf(debugFP, NULL);
3620   }
3621
3622         gameInfo.outOfBook = NULL;
3623         
3624         /* Do we have the ratings? */
3625         if (strcmp(player1Name, white) == 0 &&
3626             strcmp(player2Name, black) == 0) {
3627             if (appData.debugMode)
3628               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3629                       player1Rating, player2Rating);
3630             gameInfo.whiteRating = player1Rating;
3631             gameInfo.blackRating = player2Rating;
3632         } else if (strcmp(player2Name, white) == 0 &&
3633                    strcmp(player1Name, black) == 0) {
3634             if (appData.debugMode)
3635               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3636                       player2Rating, player1Rating);
3637             gameInfo.whiteRating = player2Rating;
3638             gameInfo.blackRating = player1Rating;
3639         }
3640         player1Name[0] = player2Name[0] = NULLCHAR;
3641
3642         /* Silence shouts if requested */
3643         if (appData.quietPlay &&
3644             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3645             SendToICS(ics_prefix);
3646             SendToICS("set shout 0\n");
3647         }
3648     }
3649     
3650     /* Deal with midgame name changes */
3651     if (!newGame) {
3652         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3653             if (gameInfo.white) free(gameInfo.white);
3654             gameInfo.white = StrSave(white);
3655         }
3656         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3657             if (gameInfo.black) free(gameInfo.black);
3658             gameInfo.black = StrSave(black);
3659         }
3660     }
3661     
3662     /* Throw away game result if anything actually changes in examine mode */
3663     if (gameMode == IcsExamining && !newGame) {
3664         gameInfo.result = GameUnfinished;
3665         if (gameInfo.resultDetails != NULL) {
3666             free(gameInfo.resultDetails);
3667             gameInfo.resultDetails = NULL;
3668         }
3669     }
3670     
3671     /* In pausing && IcsExamining mode, we ignore boards coming
3672        in if they are in a different variation than we are. */
3673     if (pauseExamInvalid) return;
3674     if (pausing && gameMode == IcsExamining) {
3675         if (moveNum <= pauseExamForwardMostMove) {
3676             pauseExamInvalid = TRUE;
3677             forwardMostMove = pauseExamForwardMostMove;
3678             return;
3679         }
3680     }
3681     
3682   if (appData.debugMode) {
3683     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3684   }
3685     /* Parse the board */
3686     for (k = 0; k < ranks; k++) {
3687       for (j = 0; j < files; j++)
3688         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3689       if(gameInfo.holdingsWidth > 1) {
3690            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3691            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3692       }
3693     }
3694     CopyBoard(boards[moveNum], board);
3695     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3696     if (moveNum == 0) {
3697         startedFromSetupPosition =
3698           !CompareBoards(board, initialPosition);
3699         if(startedFromSetupPosition)
3700             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3701     }
3702
3703     /* [HGM] Set castling rights. Take the outermost Rooks,
3704        to make it also work for FRC opening positions. Note that board12
3705        is really defective for later FRC positions, as it has no way to
3706        indicate which Rook can castle if they are on the same side of King.
3707        For the initial position we grant rights to the outermost Rooks,
3708        and remember thos rights, and we then copy them on positions
3709        later in an FRC game. This means WB might not recognize castlings with
3710        Rooks that have moved back to their original position as illegal,
3711        but in ICS mode that is not its job anyway.
3712     */
3713     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3714     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3715
3716         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3717             if(board[0][i] == WhiteRook) j = i;
3718         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3719         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3720             if(board[0][i] == WhiteRook) j = i;
3721         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3722         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3723             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3724         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3725         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3726             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3727         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3728
3729         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3730         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3731             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3732         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3733             if(board[BOARD_HEIGHT-1][k] == bKing)
3734                 initialRights[5] = castlingRights[moveNum][5] = k;
3735         if(gameInfo.variant == VariantTwoKings) {
3736             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3737             if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3738             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3739         }
3740     } else { int r;
3741         r = castlingRights[moveNum][0] = initialRights[0];
3742         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3743         r = castlingRights[moveNum][1] = initialRights[1];
3744         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3745         r = castlingRights[moveNum][3] = initialRights[3];
3746         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3747         r = castlingRights[moveNum][4] = initialRights[4];
3748         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3749         /* wildcastle kludge: always assume King has rights */
3750         r = castlingRights[moveNum][2] = initialRights[2];
3751         r = castlingRights[moveNum][5] = initialRights[5];
3752     }
3753     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3754     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3755
3756     
3757     if (ics_getting_history == H_GOT_REQ_HEADER ||
3758         ics_getting_history == H_GOT_UNREQ_HEADER) {
3759         /* This was an initial position from a move list, not
3760            the current position */
3761         return;
3762     }
3763     
3764     /* Update currentMove and known move number limits */
3765     newMove = newGame || moveNum > forwardMostMove;
3766
3767     if (newGame) {
3768         forwardMostMove = backwardMostMove = currentMove = moveNum;
3769         if (gameMode == IcsExamining && moveNum == 0) {
3770           /* Workaround for ICS limitation: we are not told the wild
3771              type when starting to examine a game.  But if we ask for
3772              the move list, the move list header will tell us */
3773             ics_getting_history = H_REQUESTED;
3774             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3775             SendToICS(str);
3776         }
3777     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3778                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3779 #if ZIPPY
3780         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3781         /* [HGM] applied this also to an engine that is silently watching        */
3782         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3783             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3784             gameInfo.variant == currentlyInitializedVariant) {
3785           takeback = forwardMostMove - moveNum;
3786           for (i = 0; i < takeback; i++) {
3787             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3788             SendToProgram("undo\n", &first);
3789           }
3790         }
3791 #endif
3792
3793         forwardMostMove = moveNum;
3794         if (!pausing || currentMove > forwardMostMove)
3795           currentMove = forwardMostMove;
3796     } else {
3797         /* New part of history that is not contiguous with old part */ 
3798         if (pausing && gameMode == IcsExamining) {
3799             pauseExamInvalid = TRUE;
3800             forwardMostMove = pauseExamForwardMostMove;
3801             return;
3802         }
3803         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3804 #if ZIPPY
3805             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3806                 // [HGM] when we will receive the move list we now request, it will be
3807                 // fed to the engine from the first move on. So if the engine is not
3808                 // in the initial position now, bring it there.
3809                 InitChessProgram(&first, 0);
3810             }
3811 #endif
3812             ics_getting_history = H_REQUESTED;
3813             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3814             SendToICS(str);
3815         }
3816         forwardMostMove = backwardMostMove = currentMove = moveNum;
3817     }
3818     
3819     /* Update the clocks */
3820     if (strchr(elapsed_time, '.')) {
3821       /* Time is in ms */
3822       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3823       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3824     } else {
3825       /* Time is in seconds */
3826       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3827       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3828     }
3829       
3830
3831 #if ZIPPY
3832     if (appData.zippyPlay && newGame &&
3833         gameMode != IcsObserving && gameMode != IcsIdle &&
3834         gameMode != IcsExamining)
3835       ZippyFirstBoard(moveNum, basetime, increment);
3836 #endif
3837     
3838     /* Put the move on the move list, first converting
3839        to canonical algebraic form. */
3840     if (moveNum > 0) {
3841   if (appData.debugMode) {
3842     if (appData.debugMode) { int f = forwardMostMove;
3843         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3844                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3845     }
3846     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3847     fprintf(debugFP, "moveNum = %d\n", moveNum);
3848     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3849     setbuf(debugFP, NULL);
3850   }
3851         if (moveNum <= backwardMostMove) {
3852             /* We don't know what the board looked like before
3853                this move.  Punt. */
3854             strcpy(parseList[moveNum - 1], move_str);
3855             strcat(parseList[moveNum - 1], " ");
3856             strcat(parseList[moveNum - 1], elapsed_time);
3857             moveList[moveNum - 1][0] = NULLCHAR;
3858         } else if (strcmp(move_str, "none") == 0) {
3859             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3860             /* Again, we don't know what the board looked like;
3861                this is really the start of the game. */
3862             parseList[moveNum - 1][0] = NULLCHAR;
3863             moveList[moveNum - 1][0] = NULLCHAR;
3864             backwardMostMove = moveNum;
3865             startedFromSetupPosition = TRUE;
3866             fromX = fromY = toX = toY = -1;
3867         } else {
3868           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3869           //                 So we parse the long-algebraic move string in stead of the SAN move
3870           int valid; char buf[MSG_SIZ], *prom;
3871
3872           // str looks something like "Q/a1-a2"; kill the slash
3873           if(str[1] == '/') 
3874                 sprintf(buf, "%c%s", str[0], str+2);
3875           else  strcpy(buf, str); // might be castling
3876           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3877                 strcat(buf, prom); // long move lacks promo specification!
3878           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3879                 if(appData.debugMode) 
3880                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3881                 strcpy(move_str, buf);
3882           }
3883           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3884                                 &fromX, &fromY, &toX, &toY, &promoChar)
3885                || ParseOneMove(buf, moveNum - 1, &moveType,
3886                                 &fromX, &fromY, &toX, &toY, &promoChar);
3887           // end of long SAN patch
3888           if (valid) {
3889             (void) CoordsToAlgebraic(boards[moveNum - 1],
3890                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3891                                      fromY, fromX, toY, toX, promoChar,
3892                                      parseList[moveNum-1]);
3893             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3894                              castlingRights[moveNum]) ) {
3895               case MT_NONE:
3896               case MT_STALEMATE:
3897               default:
3898                 break;
3899               case MT_CHECK:
3900                 if(gameInfo.variant != VariantShogi)
3901                     strcat(parseList[moveNum - 1], "+");
3902                 break;
3903               case MT_CHECKMATE:
3904               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3905                 strcat(parseList[moveNum - 1], "#");
3906                 break;
3907             }
3908             strcat(parseList[moveNum - 1], " ");
3909             strcat(parseList[moveNum - 1], elapsed_time);
3910             /* currentMoveString is set as a side-effect of ParseOneMove */
3911             strcpy(moveList[moveNum - 1], currentMoveString);
3912             strcat(moveList[moveNum - 1], "\n");
3913           } else {
3914             /* Move from ICS was illegal!?  Punt. */
3915   if (appData.debugMode) {
3916     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3917     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3918   }
3919             strcpy(parseList[moveNum - 1], move_str);
3920             strcat(parseList[moveNum - 1], " ");
3921             strcat(parseList[moveNum - 1], elapsed_time);
3922             moveList[moveNum - 1][0] = NULLCHAR;
3923             fromX = fromY = toX = toY = -1;
3924           }
3925         }
3926   if (appData.debugMode) {
3927     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3928     setbuf(debugFP, NULL);
3929   }
3930
3931 #if ZIPPY
3932         /* Send move to chess program (BEFORE animating it). */
3933         if (appData.zippyPlay && !newGame && newMove && 
3934            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3935
3936             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3937                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3938                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3939                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3940                             move_str);
3941                     DisplayError(str, 0);
3942                 } else {
3943                     if (first.sendTime) {
3944                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3945                     }
3946                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3947                     if (firstMove && !bookHit) {
3948                         firstMove = FALSE;
3949                         if (first.useColors) {
3950                           SendToProgram(gameMode == IcsPlayingWhite ?
3951                                         "white\ngo\n" :
3952                                         "black\ngo\n", &first);
3953                         } else {
3954                           SendToProgram("go\n", &first);
3955                         }
3956                         first.maybeThinking = TRUE;
3957                     }
3958                 }
3959             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3960               if (moveList[moveNum - 1][0] == NULLCHAR) {
3961                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3962                 DisplayError(str, 0);
3963               } else {
3964                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3965                 SendMoveToProgram(moveNum - 1, &first);
3966               }
3967             }
3968         }
3969 #endif
3970     }
3971
3972     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3973         /* If move comes from a remote source, animate it.  If it
3974            isn't remote, it will have already been animated. */
3975         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3976             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3977         }
3978         if (!pausing && appData.highlightLastMove) {
3979             SetHighlights(fromX, fromY, toX, toY);
3980         }
3981     }
3982     
3983     /* Start the clocks */
3984     whiteFlag = blackFlag = FALSE;
3985     appData.clockMode = !(basetime == 0 && increment == 0);
3986     if (ticking == 0) {
3987       ics_clock_paused = TRUE;
3988       StopClocks();
3989     } else if (ticking == 1) {
3990       ics_clock_paused = FALSE;
3991     }
3992     if (gameMode == IcsIdle ||
3993         relation == RELATION_OBSERVING_STATIC ||
3994         relation == RELATION_EXAMINING ||
3995         ics_clock_paused)
3996       DisplayBothClocks();
3997     else
3998       StartClocks();
3999     
4000     /* Display opponents and material strengths */
4001     if (gameInfo.variant != VariantBughouse &&
4002         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4003         if (tinyLayout || smallLayout) {
4004             if(gameInfo.variant == VariantNormal)
4005                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4006                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4007                     basetime, increment);
4008             else
4009                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4010                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4011                     basetime, increment, (int) gameInfo.variant);
4012         } else {
4013             if(gameInfo.variant == VariantNormal)
4014                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4015                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4016                     basetime, increment);
4017             else
4018                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4019                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4020                     basetime, increment, VariantName(gameInfo.variant));
4021         }
4022         DisplayTitle(str);
4023   if (appData.debugMode) {
4024     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4025   }
4026     }
4027
4028    
4029     /* Display the board */
4030     if (!pausing && !appData.noGUI) {
4031       
4032       if (appData.premove)
4033           if (!gotPremove || 
4034              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4035              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4036               ClearPremoveHighlights();
4037
4038       DrawPosition(FALSE, boards[currentMove]);
4039       DisplayMove(moveNum - 1);
4040       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4041             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4042               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4043         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4044       }
4045     }
4046
4047     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4048 #if ZIPPY
4049     if(bookHit) { // [HGM] book: simulate book reply
4050         static char bookMove[MSG_SIZ]; // a bit generous?
4051
4052         programStats.nodes = programStats.depth = programStats.time = 
4053         programStats.score = programStats.got_only_move = 0;
4054         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4055
4056         strcpy(bookMove, "move ");
4057         strcat(bookMove, bookHit);
4058         HandleMachineMove(bookMove, &first);
4059     }
4060 #endif
4061 }
4062
4063 void
4064 GetMoveListEvent()
4065 {
4066     char buf[MSG_SIZ];
4067     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4068         ics_getting_history = H_REQUESTED;
4069         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4070         SendToICS(buf);
4071     }
4072 }
4073
4074 void
4075 AnalysisPeriodicEvent(force)
4076      int force;
4077 {
4078     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4079          && !force) || !appData.periodicUpdates)
4080       return;
4081
4082     /* Send . command to Crafty to collect stats */
4083     SendToProgram(".\n", &first);
4084
4085     /* Don't send another until we get a response (this makes
4086        us stop sending to old Crafty's which don't understand
4087        the "." command (sending illegal cmds resets node count & time,
4088        which looks bad)) */
4089     programStats.ok_to_send = 0;
4090 }
4091
4092 void ics_update_width(new_width)
4093         int new_width;
4094 {
4095         ics_printf("set width %d\n", new_width);
4096 }
4097
4098 void
4099 SendMoveToProgram(moveNum, cps)
4100      int moveNum;
4101      ChessProgramState *cps;
4102 {
4103     char buf[MSG_SIZ];
4104
4105     if (cps->useUsermove) {
4106       SendToProgram("usermove ", cps);
4107     }
4108     if (cps->useSAN) {
4109       char *space;
4110       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4111         int len = space - parseList[moveNum];
4112         memcpy(buf, parseList[moveNum], len);
4113         buf[len++] = '\n';
4114         buf[len] = NULLCHAR;
4115       } else {
4116         sprintf(buf, "%s\n", parseList[moveNum]);
4117       }
4118       SendToProgram(buf, cps);
4119     } else {
4120       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4121         AlphaRank(moveList[moveNum], 4);
4122         SendToProgram(moveList[moveNum], cps);
4123         AlphaRank(moveList[moveNum], 4); // and back
4124       } else
4125       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4126        * the engine. It would be nice to have a better way to identify castle 
4127        * moves here. */
4128       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4129                                                                          && cps->useOOCastle) {
4130         int fromX = moveList[moveNum][0] - AAA; 
4131         int fromY = moveList[moveNum][1] - ONE;
4132         int toX = moveList[moveNum][2] - AAA; 
4133         int toY = moveList[moveNum][3] - ONE;
4134         if((boards[moveNum][fromY][fromX] == WhiteKing 
4135             && boards[moveNum][toY][toX] == WhiteRook)
4136            || (boards[moveNum][fromY][fromX] == BlackKing 
4137                && boards[moveNum][toY][toX] == BlackRook)) {
4138           if(toX > fromX) SendToProgram("O-O\n", cps);
4139           else SendToProgram("O-O-O\n", cps);
4140         }
4141         else SendToProgram(moveList[moveNum], cps);
4142       }
4143       else SendToProgram(moveList[moveNum], cps);
4144       /* End of additions by Tord */
4145     }
4146
4147     /* [HGM] setting up the opening has brought engine in force mode! */
4148     /*       Send 'go' if we are in a mode where machine should play. */
4149     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4150         (gameMode == TwoMachinesPlay   ||
4151 #ifdef ZIPPY
4152          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4153 #endif
4154          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4155         SendToProgram("go\n", cps);
4156   if (appData.debugMode) {
4157     fprintf(debugFP, "(extra)\n");
4158   }
4159     }
4160     setboardSpoiledMachineBlack = 0;
4161 }
4162
4163 void
4164 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4165      ChessMove moveType;
4166      int fromX, fromY, toX, toY;
4167 {
4168     char user_move[MSG_SIZ];
4169
4170     switch (moveType) {
4171       default:
4172         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4173                 (int)moveType, fromX, fromY, toX, toY);
4174         DisplayError(user_move + strlen("say "), 0);
4175         break;
4176       case WhiteKingSideCastle:
4177       case BlackKingSideCastle:
4178       case WhiteQueenSideCastleWild:
4179       case BlackQueenSideCastleWild:
4180       /* PUSH Fabien */
4181       case WhiteHSideCastleFR:
4182       case BlackHSideCastleFR:
4183       /* POP Fabien */
4184         sprintf(user_move, "o-o\n");
4185         break;
4186       case WhiteQueenSideCastle:
4187       case BlackQueenSideCastle:
4188       case WhiteKingSideCastleWild:
4189       case BlackKingSideCastleWild:
4190       /* PUSH Fabien */
4191       case WhiteASideCastleFR:
4192       case BlackASideCastleFR:
4193       /* POP Fabien */
4194         sprintf(user_move, "o-o-o\n");
4195         break;
4196       case WhitePromotionQueen:
4197       case BlackPromotionQueen:
4198       case WhitePromotionRook:
4199       case BlackPromotionRook:
4200       case WhitePromotionBishop:
4201       case BlackPromotionBishop:
4202       case WhitePromotionKnight:
4203       case BlackPromotionKnight:
4204       case WhitePromotionKing:
4205       case BlackPromotionKing:
4206       case WhitePromotionChancellor:
4207       case BlackPromotionChancellor:
4208       case WhitePromotionArchbishop:
4209       case BlackPromotionArchbishop:
4210         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4211             sprintf(user_move, "%c%c%c%c=%c\n",
4212                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4213                 PieceToChar(WhiteFerz));
4214         else if(gameInfo.variant == VariantGreat)
4215             sprintf(user_move, "%c%c%c%c=%c\n",
4216                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4217                 PieceToChar(WhiteMan));
4218         else
4219             sprintf(user_move, "%c%c%c%c=%c\n",
4220                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4221                 PieceToChar(PromoPiece(moveType)));
4222         break;
4223       case WhiteDrop:
4224       case BlackDrop:
4225         sprintf(user_move, "%c@%c%c\n",
4226                 ToUpper(PieceToChar((ChessSquare) fromX)),
4227                 AAA + toX, ONE + toY);
4228         break;
4229       case NormalMove:
4230       case WhiteCapturesEnPassant:
4231       case BlackCapturesEnPassant:
4232       case IllegalMove:  /* could be a variant we don't quite understand */
4233         sprintf(user_move, "%c%c%c%c\n",
4234                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4235         break;
4236     }
4237     SendToICS(user_move);
4238     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4239         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4240 }
4241
4242 void
4243 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4244      int rf, ff, rt, ft;
4245      char promoChar;
4246      char move[7];
4247 {
4248     if (rf == DROP_RANK) {
4249         sprintf(move, "%c@%c%c\n",
4250                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4251     } else {
4252         if (promoChar == 'x' || promoChar == NULLCHAR) {
4253             sprintf(move, "%c%c%c%c\n",
4254                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4255         } else {
4256             sprintf(move, "%c%c%c%c%c\n",
4257                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4258         }
4259     }
4260 }
4261
4262 void
4263 ProcessICSInitScript(f)
4264      FILE *f;
4265 {
4266     char buf[MSG_SIZ];
4267
4268     while (fgets(buf, MSG_SIZ, f)) {
4269         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4270     }
4271
4272     fclose(f);
4273 }
4274
4275
4276 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4277 void
4278 AlphaRank(char *move, int n)
4279 {
4280 //    char *p = move, c; int x, y;
4281
4282     if (appData.debugMode) {
4283         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4284     }
4285
4286     if(move[1]=='*' && 
4287        move[2]>='0' && move[2]<='9' &&
4288        move[3]>='a' && move[3]<='x'    ) {
4289         move[1] = '@';
4290         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4291         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4292     } else
4293     if(move[0]>='0' && move[0]<='9' &&
4294        move[1]>='a' && move[1]<='x' &&
4295        move[2]>='0' && move[2]<='9' &&
4296        move[3]>='a' && move[3]<='x'    ) {
4297         /* input move, Shogi -> normal */
4298         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4299         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4300         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4301         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4302     } else
4303     if(move[1]=='@' &&
4304        move[3]>='0' && move[3]<='9' &&
4305        move[2]>='a' && move[2]<='x'    ) {
4306         move[1] = '*';
4307         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4308         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4309     } else
4310     if(
4311        move[0]>='a' && move[0]<='x' &&
4312        move[3]>='0' && move[3]<='9' &&
4313        move[2]>='a' && move[2]<='x'    ) {
4314          /* output move, normal -> Shogi */
4315         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4316         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4317         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4318         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4319         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4320     }
4321     if (appData.debugMode) {
4322         fprintf(debugFP, "   out = '%s'\n", move);
4323     }
4324 }
4325
4326 /* Parser for moves from gnuchess, ICS, or user typein box */
4327 Boolean
4328 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4329      char *move;
4330      int moveNum;
4331      ChessMove *moveType;
4332      int *fromX, *fromY, *toX, *toY;
4333      char *promoChar;
4334 {       
4335     if (appData.debugMode) {
4336         fprintf(debugFP, "move to parse: %s\n", move);
4337     }
4338     *moveType = yylexstr(moveNum, move);
4339
4340     switch (*moveType) {
4341       case WhitePromotionChancellor:
4342       case BlackPromotionChancellor:
4343       case WhitePromotionArchbishop:
4344       case BlackPromotionArchbishop:
4345       case WhitePromotionQueen:
4346       case BlackPromotionQueen:
4347       case WhitePromotionRook:
4348       case BlackPromotionRook:
4349       case WhitePromotionBishop:
4350       case BlackPromotionBishop:
4351       case WhitePromotionKnight:
4352       case BlackPromotionKnight:
4353       case WhitePromotionKing:
4354       case BlackPromotionKing:
4355       case NormalMove:
4356       case WhiteCapturesEnPassant:
4357       case BlackCapturesEnPassant:
4358       case WhiteKingSideCastle:
4359       case WhiteQueenSideCastle:
4360       case BlackKingSideCastle:
4361       case BlackQueenSideCastle:
4362       case WhiteKingSideCastleWild:
4363       case WhiteQueenSideCastleWild:
4364       case BlackKingSideCastleWild:
4365       case BlackQueenSideCastleWild:
4366       /* Code added by Tord: */
4367       case WhiteHSideCastleFR:
4368       case WhiteASideCastleFR:
4369       case BlackHSideCastleFR:
4370       case BlackASideCastleFR:
4371       /* End of code added by Tord */
4372       case IllegalMove:         /* bug or odd chess variant */
4373         *fromX = currentMoveString[0] - AAA;
4374         *fromY = currentMoveString[1] - ONE;
4375         *toX = currentMoveString[2] - AAA;
4376         *toY = currentMoveString[3] - ONE;
4377         *promoChar = currentMoveString[4];
4378         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4379             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4380     if (appData.debugMode) {
4381         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4382     }
4383             *fromX = *fromY = *toX = *toY = 0;
4384             return FALSE;
4385         }
4386         if (appData.testLegality) {
4387           return (*moveType != IllegalMove);
4388         } else {
4389           return !(*fromX == *toX && *fromY == *toY);
4390         }
4391
4392       case WhiteDrop:
4393       case BlackDrop:
4394         *fromX = *moveType == WhiteDrop ?
4395           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4396           (int) CharToPiece(ToLower(currentMoveString[0]));
4397         *fromY = DROP_RANK;
4398         *toX = currentMoveString[2] - AAA;
4399         *toY = currentMoveString[3] - ONE;
4400         *promoChar = NULLCHAR;
4401         return TRUE;
4402
4403       case AmbiguousMove:
4404       case ImpossibleMove:
4405       case (ChessMove) 0:       /* end of file */
4406       case ElapsedTime:
4407       case Comment:
4408       case PGNTag:
4409       case NAG:
4410       case WhiteWins:
4411       case BlackWins:
4412       case GameIsDrawn:
4413       default:
4414     if (appData.debugMode) {
4415         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4416     }
4417         /* bug? */
4418         *fromX = *fromY = *toX = *toY = 0;
4419         *promoChar = NULLCHAR;
4420         return FALSE;
4421     }
4422 }
4423
4424 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4425 // All positions will have equal probability, but the current method will not provide a unique
4426 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4427 #define DARK 1
4428 #define LITE 2
4429 #define ANY 3
4430
4431 int squaresLeft[4];
4432 int piecesLeft[(int)BlackPawn];
4433 int seed, nrOfShuffles;
4434
4435 void GetPositionNumber()
4436 {       // sets global variable seed
4437         int i;
4438
4439         seed = appData.defaultFrcPosition;
4440         if(seed < 0) { // randomize based on time for negative FRC position numbers
4441                 for(i=0; i<50; i++) seed += random();
4442                 seed = random() ^ random() >> 8 ^ random() << 8;
4443                 if(seed<0) seed = -seed;
4444         }
4445 }
4446
4447 int put(Board board, int pieceType, int rank, int n, int shade)
4448 // put the piece on the (n-1)-th empty squares of the given shade
4449 {
4450         int i;
4451
4452         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4453                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4454                         board[rank][i] = (ChessSquare) pieceType;
4455                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4456                         squaresLeft[ANY]--;
4457                         piecesLeft[pieceType]--; 
4458                         return i;
4459                 }
4460         }
4461         return -1;
4462 }
4463
4464
4465 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4466 // calculate where the next piece goes, (any empty square), and put it there
4467 {
4468         int i;
4469
4470         i = seed % squaresLeft[shade];
4471         nrOfShuffles *= squaresLeft[shade];
4472         seed /= squaresLeft[shade];
4473         put(board, pieceType, rank, i, shade);
4474 }
4475
4476 void AddTwoPieces(Board board, int pieceType, int rank)
4477 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4478 {
4479         int i, n=squaresLeft[ANY], j=n-1, k;
4480
4481         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4482         i = seed % k;  // pick one
4483         nrOfShuffles *= k;
4484         seed /= k;
4485         while(i >= j) i -= j--;
4486         j = n - 1 - j; i += j;
4487         put(board, pieceType, rank, j, ANY);
4488         put(board, pieceType, rank, i, ANY);
4489 }
4490
4491 void SetUpShuffle(Board board, int number)
4492 {
4493         int i, p, first=1;
4494
4495         GetPositionNumber(); nrOfShuffles = 1;
4496
4497         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4498         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4499         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4500
4501         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4502
4503         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4504             p = (int) board[0][i];
4505             if(p < (int) BlackPawn) piecesLeft[p] ++;
4506             board[0][i] = EmptySquare;
4507         }
4508
4509         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4510             // shuffles restricted to allow normal castling put KRR first
4511             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4512                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4513             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4514                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4515             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4516                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4517             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4518                 put(board, WhiteRook, 0, 0, ANY);
4519             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4520         }
4521
4522         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4523             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4524             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4525                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4526                 while(piecesLeft[p] >= 2) {
4527                     AddOnePiece(board, p, 0, LITE);
4528                     AddOnePiece(board, p, 0, DARK);
4529                 }
4530                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4531             }
4532
4533         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4534             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4535             // but we leave King and Rooks for last, to possibly obey FRC restriction
4536             if(p == (int)WhiteRook) continue;
4537             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4538             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4539         }
4540
4541         // now everything is placed, except perhaps King (Unicorn) and Rooks
4542
4543         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4544             // Last King gets castling rights
4545             while(piecesLeft[(int)WhiteUnicorn]) {
4546                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4547                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4548             }
4549
4550             while(piecesLeft[(int)WhiteKing]) {
4551                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4552                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4553             }
4554
4555
4556         } else {
4557             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4558             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4559         }
4560
4561         // Only Rooks can be left; simply place them all
4562         while(piecesLeft[(int)WhiteRook]) {
4563                 i = put(board, WhiteRook, 0, 0, ANY);
4564                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4565                         if(first) {
4566                                 first=0;
4567                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4568                         }
4569                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4570                 }
4571         }
4572         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4573             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4574         }
4575
4576         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4577 }
4578
4579 int SetCharTable( char *table, const char * map )
4580 /* [HGM] moved here from winboard.c because of its general usefulness */
4581 /*       Basically a safe strcpy that uses the last character as King */
4582 {
4583     int result = FALSE; int NrPieces;
4584
4585     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4586                     && NrPieces >= 12 && !(NrPieces&1)) {
4587         int i; /* [HGM] Accept even length from 12 to 34 */
4588
4589         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4590         for( i=0; i<NrPieces/2-1; i++ ) {
4591             table[i] = map[i];
4592             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4593         }
4594         table[(int) WhiteKing]  = map[NrPieces/2-1];
4595         table[(int) BlackKing]  = map[NrPieces-1];
4596
4597         result = TRUE;
4598     }
4599
4600     return result;
4601 }
4602
4603 void Prelude(Board board)
4604 {       // [HGM] superchess: random selection of exo-pieces
4605         int i, j, k; ChessSquare p; 
4606         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4607
4608         GetPositionNumber(); // use FRC position number
4609
4610         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4611             SetCharTable(pieceToChar, appData.pieceToCharTable);
4612             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4613                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4614         }
4615
4616         j = seed%4;                 seed /= 4; 
4617         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4618         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4619         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4620         j = seed%3 + (seed%3 >= j); seed /= 3; 
4621         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4622         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4623         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4624         j = seed%3;                 seed /= 3; 
4625         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4626         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4627         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4628         j = seed%2 + (seed%2 >= j); seed /= 2; 
4629         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4630         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4631         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4632         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4633         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4634         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4635         put(board, exoPieces[0],    0, 0, ANY);
4636         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4637 }
4638
4639 void
4640 InitPosition(redraw)
4641      int redraw;
4642 {
4643     ChessSquare (* pieces)[BOARD_SIZE];
4644     int i, j, pawnRow, overrule,
4645     oldx = gameInfo.boardWidth,
4646     oldy = gameInfo.boardHeight,
4647     oldh = gameInfo.holdingsWidth,
4648     oldv = gameInfo.variant;
4649
4650     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4651
4652     /* [AS] Initialize pv info list [HGM] and game status */
4653     {
4654         for( i=0; i<MAX_MOVES; i++ ) {
4655             pvInfoList[i].depth = 0;
4656             epStatus[i]=EP_NONE;
4657             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4658         }
4659
4660         initialRulePlies = 0; /* 50-move counter start */
4661
4662         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4663         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4664     }
4665
4666     
4667     /* [HGM] logic here is completely changed. In stead of full positions */
4668     /* the initialized data only consist of the two backranks. The switch */
4669     /* selects which one we will use, which is than copied to the Board   */
4670     /* initialPosition, which for the rest is initialized by Pawns and    */
4671     /* empty squares. This initial position is then copied to boards[0],  */
4672     /* possibly after shuffling, so that it remains available.            */
4673
4674     gameInfo.holdingsWidth = 0; /* default board sizes */
4675     gameInfo.boardWidth    = 8;
4676     gameInfo.boardHeight   = 8;
4677     gameInfo.holdingsSize  = 0;
4678     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4679     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4680     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4681
4682     switch (gameInfo.variant) {
4683     case VariantFischeRandom:
4684       shuffleOpenings = TRUE;
4685     default:
4686       pieces = FIDEArray;
4687       break;
4688     case VariantShatranj:
4689       pieces = ShatranjArray;
4690       nrCastlingRights = 0;
4691       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4692       break;
4693     case VariantMakruk:
4694       pieces = makrukArray;
4695       nrCastlingRights = 0;
4696       startedFromSetupPosition = TRUE;
4697       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4698       break;
4699     case VariantTwoKings:
4700       pieces = twoKingsArray;
4701       break;
4702     case VariantCapaRandom:
4703       shuffleOpenings = TRUE;
4704     case VariantCapablanca:
4705       pieces = CapablancaArray;
4706       gameInfo.boardWidth = 10;
4707       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4708       break;
4709     case VariantGothic:
4710       pieces = GothicArray;
4711       gameInfo.boardWidth = 10;
4712       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4713       break;
4714     case VariantJanus:
4715       pieces = JanusArray;
4716       gameInfo.boardWidth = 10;
4717       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4718       nrCastlingRights = 6;
4719         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4720         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4721         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4722         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4723         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4724         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4725       break;
4726     case VariantFalcon:
4727       pieces = FalconArray;
4728       gameInfo.boardWidth = 10;
4729       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4730       break;
4731     case VariantXiangqi:
4732       pieces = XiangqiArray;
4733       gameInfo.boardWidth  = 9;
4734       gameInfo.boardHeight = 10;
4735       nrCastlingRights = 0;
4736       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4737       break;
4738     case VariantShogi:
4739       pieces = ShogiArray;
4740       gameInfo.boardWidth  = 9;
4741       gameInfo.boardHeight = 9;
4742       gameInfo.holdingsSize = 7;
4743       nrCastlingRights = 0;
4744       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4745       break;
4746     case VariantCourier:
4747       pieces = CourierArray;
4748       gameInfo.boardWidth  = 12;
4749       nrCastlingRights = 0;
4750       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4751       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4752       break;
4753     case VariantKnightmate:
4754       pieces = KnightmateArray;
4755       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4756       break;
4757     case VariantFairy:
4758       pieces = fairyArray;
4759       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4760       break;
4761     case VariantGreat:
4762       pieces = GreatArray;
4763       gameInfo.boardWidth = 10;
4764       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4765       gameInfo.holdingsSize = 8;
4766       break;
4767     case VariantSuper:
4768       pieces = FIDEArray;
4769       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4770       gameInfo.holdingsSize = 8;
4771       startedFromSetupPosition = TRUE;
4772       break;
4773     case VariantCrazyhouse:
4774     case VariantBughouse:
4775       pieces = FIDEArray;
4776       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4777       gameInfo.holdingsSize = 5;
4778       break;
4779     case VariantWildCastle:
4780       pieces = FIDEArray;
4781       /* !!?shuffle with kings guaranteed to be on d or e file */
4782       shuffleOpenings = 1;
4783       break;
4784     case VariantNoCastle:
4785       pieces = FIDEArray;
4786       nrCastlingRights = 0;
4787       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4788       /* !!?unconstrained back-rank shuffle */
4789       shuffleOpenings = 1;
4790       break;
4791     }
4792
4793     overrule = 0;
4794     if(appData.NrFiles >= 0) {
4795         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4796         gameInfo.boardWidth = appData.NrFiles;
4797     }
4798     if(appData.NrRanks >= 0) {
4799         gameInfo.boardHeight = appData.NrRanks;
4800     }
4801     if(appData.holdingsSize >= 0) {
4802         i = appData.holdingsSize;
4803         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4804         gameInfo.holdingsSize = i;
4805     }
4806     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4807     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4808         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4809
4810     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4811     if(pawnRow < 1) pawnRow = 1;
4812     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4813
4814     /* User pieceToChar list overrules defaults */
4815     if(appData.pieceToCharTable != NULL)
4816         SetCharTable(pieceToChar, appData.pieceToCharTable);
4817
4818     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4819
4820         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4821             s = (ChessSquare) 0; /* account holding counts in guard band */
4822         for( i=0; i<BOARD_HEIGHT; i++ )
4823             initialPosition[i][j] = s;
4824
4825         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4826         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4827         initialPosition[pawnRow][j] = WhitePawn;
4828         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4829         if(gameInfo.variant == VariantXiangqi) {
4830             if(j&1) {
4831                 initialPosition[pawnRow][j] = 
4832                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4833                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4834                    initialPosition[2][j] = WhiteCannon;
4835                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4836                 }
4837             }
4838         }
4839         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4840     }
4841     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4842
4843             j=BOARD_LEFT+1;
4844             initialPosition[1][j] = WhiteBishop;
4845             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4846             j=BOARD_RGHT-2;
4847             initialPosition[1][j] = WhiteRook;
4848             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4849     }
4850
4851     if( nrCastlingRights == -1) {
4852         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4853         /*       This sets default castling rights from none to normal corners   */
4854         /* Variants with other castling rights must set them themselves above    */
4855         nrCastlingRights = 6;
4856        
4857         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4858         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4859         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4860         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4861         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4862         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4863      }
4864
4865      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4866      if(gameInfo.variant == VariantGreat) { // promotion commoners
4867         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4868         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4869         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4870         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4871      }
4872   if (appData.debugMode) {
4873     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4874   }
4875     if(shuffleOpenings) {
4876         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4877         startedFromSetupPosition = TRUE;
4878     }
4879     if(startedFromPositionFile) {
4880       /* [HGM] loadPos: use PositionFile for every new game */
4881       CopyBoard(initialPosition, filePosition);
4882       for(i=0; i<nrCastlingRights; i++)
4883           castlingRights[0][i] = initialRights[i] = fileRights[i];
4884       startedFromSetupPosition = TRUE;
4885     }
4886
4887     CopyBoard(boards[0], initialPosition);
4888
4889     if(oldx != gameInfo.boardWidth ||
4890        oldy != gameInfo.boardHeight ||
4891        oldh != gameInfo.holdingsWidth
4892 #ifdef GOTHIC
4893        || oldv == VariantGothic ||        // For licensing popups
4894        gameInfo.variant == VariantGothic
4895 #endif
4896 #ifdef FALCON
4897        || oldv == VariantFalcon ||
4898        gameInfo.variant == VariantFalcon
4899 #endif
4900                                          )
4901             InitDrawingSizes(-2 ,0);
4902
4903     if (redraw)
4904       DrawPosition(TRUE, boards[currentMove]);
4905 }
4906
4907 void
4908 SendBoard(cps, moveNum)
4909      ChessProgramState *cps;
4910      int moveNum;
4911 {
4912     char message[MSG_SIZ];
4913     
4914     if (cps->useSetboard) {
4915       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4916       sprintf(message, "setboard %s\n", fen);
4917       SendToProgram(message, cps);
4918       free(fen);
4919
4920     } else {
4921       ChessSquare *bp;
4922       int i, j;
4923       /* Kludge to set black to move, avoiding the troublesome and now
4924        * deprecated "black" command.
4925        */
4926       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4927
4928       SendToProgram("edit\n", cps);
4929       SendToProgram("#\n", cps);
4930       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4931         bp = &boards[moveNum][i][BOARD_LEFT];
4932         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4933           if ((int) *bp < (int) BlackPawn) {
4934             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4935                     AAA + j, ONE + i);
4936             if(message[0] == '+' || message[0] == '~') {
4937                 sprintf(message, "%c%c%c+\n",
4938                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4939                         AAA + j, ONE + i);
4940             }
4941             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4942                 message[1] = BOARD_RGHT   - 1 - j + '1';
4943                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4944             }
4945             SendToProgram(message, cps);
4946           }
4947         }
4948       }
4949     
4950       SendToProgram("c\n", cps);
4951       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4952         bp = &boards[moveNum][i][BOARD_LEFT];
4953         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4954           if (((int) *bp != (int) EmptySquare)
4955               && ((int) *bp >= (int) BlackPawn)) {
4956             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4957                     AAA + j, ONE + i);
4958             if(message[0] == '+' || message[0] == '~') {
4959                 sprintf(message, "%c%c%c+\n",
4960                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4961                         AAA + j, ONE + i);
4962             }
4963             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4964                 message[1] = BOARD_RGHT   - 1 - j + '1';
4965                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4966             }
4967             SendToProgram(message, cps);
4968           }
4969         }
4970       }
4971     
4972       SendToProgram(".\n", cps);
4973     }
4974     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4975 }
4976
4977 int
4978 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4979 {
4980     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4981     /* [HGM] add Shogi promotions */
4982     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4983     ChessSquare piece;
4984     ChessMove moveType;
4985     Boolean premove;
4986
4987     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4988     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4989
4990     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4991       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4992         return FALSE;
4993
4994     piece = boards[currentMove][fromY][fromX];
4995     if(gameInfo.variant == VariantShogi) {
4996         promotionZoneSize = 3;
4997         highestPromotingPiece = (int)WhiteFerz;
4998     } else if(gameInfo.variant == VariantMakruk) {
4999         promotionZoneSize = 3;
5000     }
5001
5002     // next weed out all moves that do not touch the promotion zone at all
5003     if((int)piece >= BlackPawn) {
5004         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5005              return FALSE;
5006         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5007     } else {
5008         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5009            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5010     }
5011
5012     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5013
5014     // weed out mandatory Shogi promotions
5015     if(gameInfo.variant == VariantShogi) {
5016         if(piece >= BlackPawn) {
5017             if(toY == 0 && piece == BlackPawn ||
5018                toY == 0 && piece == BlackQueen ||
5019                toY <= 1 && piece == BlackKnight) {
5020                 *promoChoice = '+';
5021                 return FALSE;
5022             }
5023         } else {
5024             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5025                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5026                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5027                 *promoChoice = '+';
5028                 return FALSE;
5029             }
5030         }
5031     }
5032
5033     // weed out obviously illegal Pawn moves
5034     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5035         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5036         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5037         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5038         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5039         // note we are not allowed to test for valid (non-)capture, due to premove
5040     }
5041
5042     // we either have a choice what to promote to, or (in Shogi) whether to promote
5043     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5044         *promoChoice = PieceToChar(BlackFerz);  // no choice
5045         return FALSE;
5046     }
5047     if(appData.alwaysPromoteToQueen) { // predetermined
5048         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5049              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5050         else *promoChoice = PieceToChar(BlackQueen);
5051         return FALSE;
5052     }
5053
5054     // suppress promotion popup on illegal moves that are not premoves
5055     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5056               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5057     if(appData.testLegality && !premove) {
5058         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5059                         epStatus[currentMove], castlingRights[currentMove],
5060                         fromY, fromX, toY, toX, NULLCHAR);
5061         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5062            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5063             return FALSE;
5064     }
5065
5066     return TRUE;
5067 }
5068
5069 int
5070 InPalace(row, column)
5071      int row, column;
5072 {   /* [HGM] for Xiangqi */
5073     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5074          column < (BOARD_WIDTH + 4)/2 &&
5075          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5076     return FALSE;
5077 }
5078
5079 int
5080 PieceForSquare (x, y)
5081      int x;
5082      int y;
5083 {
5084   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5085      return -1;
5086   else
5087      return boards[currentMove][y][x];
5088 }
5089
5090 int
5091 OKToStartUserMove(x, y)
5092      int x, y;
5093 {
5094     ChessSquare from_piece;
5095     int white_piece;
5096
5097     if (matchMode) return FALSE;
5098     if (gameMode == EditPosition) return TRUE;
5099
5100     if (x >= 0 && y >= 0)
5101       from_piece = boards[currentMove][y][x];
5102     else
5103       from_piece = EmptySquare;
5104
5105     if (from_piece == EmptySquare) return FALSE;
5106
5107     white_piece = (int)from_piece >= (int)WhitePawn &&
5108       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5109
5110     switch (gameMode) {
5111       case PlayFromGameFile:
5112       case AnalyzeFile:
5113       case TwoMachinesPlay:
5114       case EndOfGame:
5115         return FALSE;
5116
5117       case IcsObserving:
5118       case IcsIdle:
5119         return FALSE;
5120
5121       case MachinePlaysWhite:
5122       case IcsPlayingBlack:
5123         if (appData.zippyPlay) return FALSE;
5124         if (white_piece) {
5125             DisplayMoveError(_("You are playing Black"));
5126             return FALSE;
5127         }
5128         break;
5129
5130       case MachinePlaysBlack:
5131       case IcsPlayingWhite:
5132         if (appData.zippyPlay) return FALSE;
5133         if (!white_piece) {
5134             DisplayMoveError(_("You are playing White"));
5135             return FALSE;
5136         }
5137         break;
5138
5139       case EditGame:
5140         if (!white_piece && WhiteOnMove(currentMove)) {
5141             DisplayMoveError(_("It is White's turn"));
5142             return FALSE;
5143         }           
5144         if (white_piece && !WhiteOnMove(currentMove)) {
5145             DisplayMoveError(_("It is Black's turn"));
5146             return FALSE;
5147         }           
5148         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5149             /* Editing correspondence game history */
5150             /* Could disallow this or prompt for confirmation */
5151             cmailOldMove = -1;
5152         }
5153         if (currentMove < forwardMostMove) {
5154             /* Discarding moves */
5155             /* Could prompt for confirmation here,
5156                but I don't think that's such a good idea */
5157             forwardMostMove = currentMove;
5158         }
5159         break;
5160
5161       case BeginningOfGame:
5162         if (appData.icsActive) return FALSE;
5163         if (!appData.noChessProgram) {
5164             if (!white_piece) {
5165                 DisplayMoveError(_("You are playing White"));
5166                 return FALSE;
5167             }
5168         }
5169         break;
5170         
5171       case Training:
5172         if (!white_piece && WhiteOnMove(currentMove)) {
5173             DisplayMoveError(_("It is White's turn"));
5174             return FALSE;
5175         }           
5176         if (white_piece && !WhiteOnMove(currentMove)) {
5177             DisplayMoveError(_("It is Black's turn"));
5178             return FALSE;
5179         }           
5180         break;
5181
5182       default:
5183       case IcsExamining:
5184         break;
5185     }
5186     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5187         && gameMode != AnalyzeFile && gameMode != Training) {
5188         DisplayMoveError(_("Displayed position is not current"));
5189         return FALSE;
5190     }
5191     return TRUE;
5192 }
5193
5194 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5195 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5196 int lastLoadGameUseList = FALSE;
5197 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5198 ChessMove lastLoadGameStart = (ChessMove) 0;
5199
5200 ChessMove
5201 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5202      int fromX, fromY, toX, toY;
5203      int promoChar;
5204      Boolean captureOwn;
5205 {
5206     ChessMove moveType;
5207     ChessSquare pdown, pup;
5208
5209     /* Check if the user is playing in turn.  This is complicated because we
5210        let the user "pick up" a piece before it is his turn.  So the piece he
5211        tried to pick up may have been captured by the time he puts it down!
5212        Therefore we use the color the user is supposed to be playing in this
5213        test, not the color of the piece that is currently on the starting
5214        square---except in EditGame mode, where the user is playing both
5215        sides; fortunately there the capture race can't happen.  (It can
5216        now happen in IcsExamining mode, but that's just too bad.  The user
5217        will get a somewhat confusing message in that case.)
5218        */
5219
5220     switch (gameMode) {
5221       case PlayFromGameFile:
5222       case AnalyzeFile:
5223       case TwoMachinesPlay:
5224       case EndOfGame:
5225       case IcsObserving:
5226       case IcsIdle:
5227         /* We switched into a game mode where moves are not accepted,
5228            perhaps while the mouse button was down. */
5229         return ImpossibleMove;
5230
5231       case MachinePlaysWhite:
5232         /* User is moving for Black */
5233         if (WhiteOnMove(currentMove)) {
5234             DisplayMoveError(_("It is White's turn"));
5235             return ImpossibleMove;
5236         }
5237         break;
5238
5239       case MachinePlaysBlack:
5240         /* User is moving for White */
5241         if (!WhiteOnMove(currentMove)) {
5242             DisplayMoveError(_("It is Black's turn"));
5243             return ImpossibleMove;
5244         }
5245         break;
5246
5247       case EditGame:
5248       case IcsExamining:
5249       case BeginningOfGame:
5250       case AnalyzeMode:
5251       case Training:
5252         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5253             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5254             /* User is moving for Black */
5255             if (WhiteOnMove(currentMove)) {
5256                 DisplayMoveError(_("It is White's turn"));
5257                 return ImpossibleMove;
5258             }
5259         } else {
5260             /* User is moving for White */
5261             if (!WhiteOnMove(currentMove)) {
5262                 DisplayMoveError(_("It is Black's turn"));
5263                 return ImpossibleMove;
5264             }
5265         }
5266         break;
5267
5268       case IcsPlayingBlack:
5269         /* User is moving for Black */
5270         if (WhiteOnMove(currentMove)) {
5271             if (!appData.premove) {
5272                 DisplayMoveError(_("It is White's turn"));
5273             } else if (toX >= 0 && toY >= 0) {
5274                 premoveToX = toX;
5275                 premoveToY = toY;
5276                 premoveFromX = fromX;
5277                 premoveFromY = fromY;
5278                 premovePromoChar = promoChar;
5279                 gotPremove = 1;
5280                 if (appData.debugMode) 
5281                     fprintf(debugFP, "Got premove: fromX %d,"
5282                             "fromY %d, toX %d, toY %d\n",
5283                             fromX, fromY, toX, toY);
5284             }
5285             return ImpossibleMove;
5286         }
5287         break;
5288
5289       case IcsPlayingWhite:
5290         /* User is moving for White */
5291         if (!WhiteOnMove(currentMove)) {
5292             if (!appData.premove) {
5293                 DisplayMoveError(_("It is Black's turn"));
5294             } else if (toX >= 0 && toY >= 0) {
5295                 premoveToX = toX;
5296                 premoveToY = toY;
5297                 premoveFromX = fromX;
5298                 premoveFromY = fromY;
5299                 premovePromoChar = promoChar;
5300                 gotPremove = 1;
5301                 if (appData.debugMode) 
5302                     fprintf(debugFP, "Got premove: fromX %d,"
5303                             "fromY %d, toX %d, toY %d\n",
5304                             fromX, fromY, toX, toY);
5305             }
5306             return ImpossibleMove;
5307         }
5308         break;
5309
5310       default:
5311         break;
5312
5313       case EditPosition:
5314         /* EditPosition, empty square, or different color piece;
5315            click-click move is possible */
5316         if (toX == -2 || toY == -2) {
5317             boards[0][fromY][fromX] = EmptySquare;
5318             return AmbiguousMove;
5319         } else if (toX >= 0 && toY >= 0) {
5320             boards[0][toY][toX] = boards[0][fromY][fromX];
5321             boards[0][fromY][fromX] = EmptySquare;
5322             return AmbiguousMove;
5323         }
5324         return ImpossibleMove;
5325     }
5326
5327     if(toX < 0 || toY < 0) return ImpossibleMove;
5328     pdown = boards[currentMove][fromY][fromX];
5329     pup = boards[currentMove][toY][toX];
5330
5331     /* [HGM] If move started in holdings, it means a drop */
5332     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5333          if( pup != EmptySquare ) return ImpossibleMove;
5334          if(appData.testLegality) {
5335              /* it would be more logical if LegalityTest() also figured out
5336               * which drops are legal. For now we forbid pawns on back rank.
5337               * Shogi is on its own here...
5338               */
5339              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5340                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5341                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5342          }
5343          return WhiteDrop; /* Not needed to specify white or black yet */
5344     }
5345
5346     userOfferedDraw = FALSE;
5347         
5348     /* [HGM] always test for legality, to get promotion info */
5349     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5350                           epStatus[currentMove], castlingRights[currentMove],
5351                                          fromY, fromX, toY, toX, promoChar);
5352     /* [HGM] but possibly ignore an IllegalMove result */
5353     if (appData.testLegality) {
5354         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5355             DisplayMoveError(_("Illegal move"));
5356             return ImpossibleMove;
5357         }
5358     }
5359
5360     return moveType;
5361     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5362        function is made into one that returns an OK move type if FinishMove
5363        should be called. This to give the calling driver routine the
5364        opportunity to finish the userMove input with a promotion popup,
5365        without bothering the user with this for invalid or illegal moves */
5366
5367 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5368 }
5369
5370 /* Common tail of UserMoveEvent and DropMenuEvent */
5371 int
5372 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5373      ChessMove moveType;
5374      int fromX, fromY, toX, toY;
5375      /*char*/int promoChar;
5376 {
5377     char *bookHit = 0;
5378
5379     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5380         // [HGM] superchess: suppress promotions to non-available piece
5381         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5382         if(WhiteOnMove(currentMove)) {
5383             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5384         } else {
5385             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5386         }
5387     }
5388
5389     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5390        move type in caller when we know the move is a legal promotion */
5391     if(moveType == NormalMove && promoChar)
5392         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5393
5394     /* [HGM] convert drag-and-drop piece drops to standard form */
5395     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5396          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5397            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5398                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5399            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5400            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5401            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5402            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5403          fromY = DROP_RANK;
5404     }
5405
5406     /* [HGM] <popupFix> The following if has been moved here from
5407        UserMoveEvent(). Because it seemed to belong here (why not allow
5408        piece drops in training games?), and because it can only be
5409        performed after it is known to what we promote. */
5410     if (gameMode == Training) {
5411       /* compare the move played on the board to the next move in the
5412        * game. If they match, display the move and the opponent's response. 
5413        * If they don't match, display an error message.
5414        */
5415       int saveAnimate;
5416       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5417       CopyBoard(testBoard, boards[currentMove]);
5418       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5419
5420       if (CompareBoards(testBoard, boards[currentMove+1])) {
5421         ForwardInner(currentMove+1);
5422
5423         /* Autoplay the opponent's response.
5424          * if appData.animate was TRUE when Training mode was entered,
5425          * the response will be animated.
5426          */
5427         saveAnimate = appData.animate;
5428         appData.animate = animateTraining;
5429         ForwardInner(currentMove+1);
5430         appData.animate = saveAnimate;
5431
5432         /* check for the end of the game */
5433         if (currentMove >= forwardMostMove) {
5434           gameMode = PlayFromGameFile;
5435           ModeHighlight();
5436           SetTrainingModeOff();
5437           DisplayInformation(_("End of game"));
5438         }
5439       } else {
5440         DisplayError(_("Incorrect move"), 0);
5441       }
5442       return 1;
5443     }
5444
5445   /* Ok, now we know that the move is good, so we can kill
5446      the previous line in Analysis Mode */
5447   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5448     forwardMostMove = currentMove;
5449   }
5450
5451   /* If we need the chess program but it's dead, restart it */
5452   ResurrectChessProgram();
5453
5454   /* A user move restarts a paused game*/
5455   if (pausing)
5456     PauseEvent();
5457
5458   thinkOutput[0] = NULLCHAR;
5459
5460   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5461
5462   if (gameMode == BeginningOfGame) {
5463     if (appData.noChessProgram) {
5464       gameMode = EditGame;
5465       SetGameInfo();
5466     } else {
5467       char buf[MSG_SIZ];
5468       gameMode = MachinePlaysBlack;
5469       StartClocks();
5470       SetGameInfo();
5471       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5472       DisplayTitle(buf);
5473       if (first.sendName) {
5474         sprintf(buf, "name %s\n", gameInfo.white);
5475         SendToProgram(buf, &first);
5476       }
5477       StartClocks();
5478     }
5479     ModeHighlight();
5480   }
5481
5482   /* Relay move to ICS or chess engine */
5483   if (appData.icsActive) {
5484     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5485         gameMode == IcsExamining) {
5486       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5487       ics_user_moved = 1;
5488     }
5489   } else {
5490     if (first.sendTime && (gameMode == BeginningOfGame ||
5491                            gameMode == MachinePlaysWhite ||
5492                            gameMode == MachinePlaysBlack)) {
5493       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5494     }
5495     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5496          // [HGM] book: if program might be playing, let it use book
5497         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5498         first.maybeThinking = TRUE;
5499     } else SendMoveToProgram(forwardMostMove-1, &first);
5500     if (currentMove == cmailOldMove + 1) {
5501       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5502     }
5503   }
5504
5505   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5506
5507   switch (gameMode) {
5508   case EditGame:
5509     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5510                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5511     case MT_NONE:
5512     case MT_CHECK:
5513       break;
5514     case MT_CHECKMATE:
5515     case MT_STAINMATE:
5516       if (WhiteOnMove(currentMove)) {
5517         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5518       } else {
5519         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5520       }
5521       break;
5522     case MT_STALEMATE:
5523       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5524       break;
5525     }
5526     break;
5527     
5528   case MachinePlaysBlack:
5529   case MachinePlaysWhite:
5530     /* disable certain menu options while machine is thinking */
5531     SetMachineThinkingEnables();
5532     break;
5533
5534   default:
5535     break;
5536   }
5537
5538   if(bookHit) { // [HGM] book: simulate book reply
5539         static char bookMove[MSG_SIZ]; // a bit generous?
5540
5541         programStats.nodes = programStats.depth = programStats.time = 
5542         programStats.score = programStats.got_only_move = 0;
5543         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5544
5545         strcpy(bookMove, "move ");
5546         strcat(bookMove, bookHit);
5547         HandleMachineMove(bookMove, &first);
5548   }
5549   return 1;
5550 }
5551
5552 void
5553 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5554      int fromX, fromY, toX, toY;
5555      int promoChar;
5556 {
5557     /* [HGM] This routine was added to allow calling of its two logical
5558        parts from other modules in the old way. Before, UserMoveEvent()
5559        automatically called FinishMove() if the move was OK, and returned
5560        otherwise. I separated the two, in order to make it possible to
5561        slip a promotion popup in between. But that it always needs two
5562        calls, to the first part, (now called UserMoveTest() ), and to
5563        FinishMove if the first part succeeded. Calls that do not need
5564        to do anything in between, can call this routine the old way. 
5565     */
5566     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5567 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5568     if(moveType == AmbiguousMove)
5569         DrawPosition(FALSE, boards[currentMove]);
5570     else if(moveType != ImpossibleMove && moveType != Comment)
5571         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5572 }
5573
5574 void LeftClick(ClickType clickType, int xPix, int yPix)
5575 {
5576     int x, y;
5577     Boolean saveAnimate;
5578     static int second = 0, promotionChoice = 0;
5579     char promoChoice = NULLCHAR;
5580
5581     if (clickType == Press) ErrorPopDown();
5582
5583     x = EventToSquare(xPix, BOARD_WIDTH);
5584     y = EventToSquare(yPix, BOARD_HEIGHT);
5585     if (!flipView && y >= 0) {
5586         y = BOARD_HEIGHT - 1 - y;
5587     }
5588     if (flipView && x >= 0) {
5589         x = BOARD_WIDTH - 1 - x;
5590     }
5591
5592     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5593         if(clickType == Release) return; // ignore upclick of click-click destination
5594         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5595         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5596         if(gameInfo.holdingsWidth && 
5597                 (WhiteOnMove(currentMove) 
5598                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5599                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5600             // click in right holdings, for determining promotion piece
5601             ChessSquare p = boards[currentMove][y][x];
5602             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5603             if(p != EmptySquare) {
5604                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5605                 fromX = fromY = -1;
5606                 return;
5607             }
5608         }
5609         DrawPosition(FALSE, boards[currentMove]);
5610         return;
5611     }
5612
5613     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5614     if(clickType == Press
5615             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5616               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5617               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5618         return;
5619
5620     if (fromX == -1) {
5621         if (clickType == Press) {
5622             /* First square */
5623             if (OKToStartUserMove(x, y)) {
5624                 fromX = x;
5625                 fromY = y;
5626                 second = 0;
5627                 DragPieceBegin(xPix, yPix);
5628                 if (appData.highlightDragging) {
5629                     SetHighlights(x, y, -1, -1);
5630                 }
5631             }
5632         }
5633         return;
5634     }
5635
5636     /* fromX != -1 */
5637     if (clickType == Press && gameMode != EditPosition) {
5638         ChessSquare fromP;
5639         ChessSquare toP;
5640         int frc;
5641
5642         // ignore off-board to clicks
5643         if(y < 0 || x < 0) return;
5644
5645         /* Check if clicking again on the same color piece */
5646         fromP = boards[currentMove][fromY][fromX];
5647         toP = boards[currentMove][y][x];
5648         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5649         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5650              WhitePawn <= toP && toP <= WhiteKing &&
5651              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5652              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5653             (BlackPawn <= fromP && fromP <= BlackKing && 
5654              BlackPawn <= toP && toP <= BlackKing &&
5655              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5656              !(fromP == BlackKing && toP == BlackRook && frc))) {
5657             /* Clicked again on same color piece -- changed his mind */
5658             second = (x == fromX && y == fromY);
5659             if (appData.highlightDragging) {
5660                 SetHighlights(x, y, -1, -1);
5661             } else {
5662                 ClearHighlights();
5663             }
5664             if (OKToStartUserMove(x, y)) {
5665                 fromX = x;
5666                 fromY = y;
5667                 DragPieceBegin(xPix, yPix);
5668             }
5669             return;
5670         }
5671         // ignore clicks on holdings
5672         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5673     }
5674
5675     if (clickType == Release && x == fromX && y == fromY) {
5676         DragPieceEnd(xPix, yPix);
5677         if (appData.animateDragging) {
5678             /* Undo animation damage if any */
5679             DrawPosition(FALSE, NULL);
5680         }
5681         if (second) {
5682             /* Second up/down in same square; just abort move */
5683             second = 0;
5684             fromX = fromY = -1;
5685             ClearHighlights();
5686             gotPremove = 0;
5687             ClearPremoveHighlights();
5688         } else {
5689             /* First upclick in same square; start click-click mode */
5690             SetHighlights(x, y, -1, -1);
5691         }
5692         return;
5693     }
5694
5695     /* we now have a different from- and (possibly off-board) to-square */
5696     /* Completed move */
5697     toX = x;
5698     toY = y;
5699     saveAnimate = appData.animate;
5700     if (clickType == Press) {
5701         /* Finish clickclick move */
5702         if (appData.animate || appData.highlightLastMove) {
5703             SetHighlights(fromX, fromY, toX, toY);
5704         } else {
5705             ClearHighlights();
5706         }
5707     } else {
5708         /* Finish drag move */
5709         if (appData.highlightLastMove) {
5710             SetHighlights(fromX, fromY, toX, toY);
5711         } else {
5712             ClearHighlights();
5713         }
5714         DragPieceEnd(xPix, yPix);
5715         /* Don't animate move and drag both */
5716         appData.animate = FALSE;
5717     }
5718
5719     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5720     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5721         ClearHighlights();
5722         fromX = fromY = -1;
5723         DrawPosition(TRUE, NULL);
5724         return;
5725     }
5726
5727     // off-board moves should not be highlighted
5728     if(x < 0 || x < 0) ClearHighlights();
5729
5730     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5731         SetHighlights(fromX, fromY, toX, toY);
5732         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5733             // [HGM] super: promotion to captured piece selected from holdings
5734             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5735             promotionChoice = TRUE;
5736             // kludge follows to temporarily execute move on display, without promoting yet
5737             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5738             boards[currentMove][toY][toX] = p;
5739             DrawPosition(FALSE, boards[currentMove]);
5740             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5741             boards[currentMove][toY][toX] = q;
5742             DisplayMessage("Click in holdings to choose piece", "");
5743             return;
5744         }
5745         PromotionPopUp();
5746     } else {
5747         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5748         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5749         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5750         fromX = fromY = -1;
5751     }
5752     appData.animate = saveAnimate;
5753     if (appData.animate || appData.animateDragging) {
5754         /* Undo animation damage if needed */
5755         DrawPosition(FALSE, NULL);
5756     }
5757 }
5758
5759 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5760 {
5761 //    char * hint = lastHint;
5762     FrontEndProgramStats stats;
5763
5764     stats.which = cps == &first ? 0 : 1;
5765     stats.depth = cpstats->depth;
5766     stats.nodes = cpstats->nodes;
5767     stats.score = cpstats->score;
5768     stats.time = cpstats->time;
5769     stats.pv = cpstats->movelist;
5770     stats.hint = lastHint;
5771     stats.an_move_index = 0;
5772     stats.an_move_count = 0;
5773
5774     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5775         stats.hint = cpstats->move_name;
5776         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5777         stats.an_move_count = cpstats->nr_moves;
5778     }
5779
5780     SetProgramStats( &stats );
5781 }
5782
5783 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5784 {   // [HGM] book: this routine intercepts moves to simulate book replies
5785     char *bookHit = NULL;
5786
5787     //first determine if the incoming move brings opponent into his book
5788     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5789         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5790     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5791     if(bookHit != NULL && !cps->bookSuspend) {
5792         // make sure opponent is not going to reply after receiving move to book position
5793         SendToProgram("force\n", cps);
5794         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5795     }
5796     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5797     // now arrange restart after book miss
5798     if(bookHit) {
5799         // after a book hit we never send 'go', and the code after the call to this routine
5800         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5801         char buf[MSG_SIZ];
5802         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5803         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5804         SendToProgram(buf, cps);
5805         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5806     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5807         SendToProgram("go\n", cps);
5808         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5809     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5810         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5811             SendToProgram("go\n", cps); 
5812         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5813     }
5814     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5815 }
5816
5817 char *savedMessage;
5818 ChessProgramState *savedState;
5819 void DeferredBookMove(void)
5820 {
5821         if(savedState->lastPing != savedState->lastPong)
5822                     ScheduleDelayedEvent(DeferredBookMove, 10);
5823         else
5824         HandleMachineMove(savedMessage, savedState);
5825 }
5826
5827 void
5828 HandleMachineMove(message, cps)
5829      char *message;
5830      ChessProgramState *cps;
5831 {
5832     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5833     char realname[MSG_SIZ];
5834     int fromX, fromY, toX, toY;
5835     ChessMove moveType;
5836     char promoChar;
5837     char *p;
5838     int machineWhite;
5839     char *bookHit;
5840
5841 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5842     /*
5843      * Kludge to ignore BEL characters
5844      */
5845     while (*message == '\007') message++;
5846
5847     /*
5848      * [HGM] engine debug message: ignore lines starting with '#' character
5849      */
5850     if(cps->debug && *message == '#') return;
5851
5852     /*
5853      * Look for book output
5854      */
5855     if (cps == &first && bookRequested) {
5856         if (message[0] == '\t' || message[0] == ' ') {
5857             /* Part of the book output is here; append it */
5858             strcat(bookOutput, message);
5859             strcat(bookOutput, "  \n");
5860             return;
5861         } else if (bookOutput[0] != NULLCHAR) {
5862             /* All of book output has arrived; display it */
5863             char *p = bookOutput;
5864             while (*p != NULLCHAR) {
5865                 if (*p == '\t') *p = ' ';
5866                 p++;
5867             }
5868             DisplayInformation(bookOutput);
5869             bookRequested = FALSE;
5870             /* Fall through to parse the current output */
5871         }
5872     }
5873
5874     /*
5875      * Look for machine move.
5876      */
5877     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5878         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5879     {
5880         /* This method is only useful on engines that support ping */
5881         if (cps->lastPing != cps->lastPong) {
5882           if (gameMode == BeginningOfGame) {
5883             /* Extra move from before last new; ignore */
5884             if (appData.debugMode) {
5885                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5886             }
5887           } else {
5888             if (appData.debugMode) {
5889                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5890                         cps->which, gameMode);
5891             }
5892
5893             SendToProgram("undo\n", cps);
5894           }
5895           return;
5896         }
5897
5898         switch (gameMode) {
5899           case BeginningOfGame:
5900             /* Extra move from before last reset; ignore */
5901             if (appData.debugMode) {
5902                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5903             }
5904             return;
5905
5906           case EndOfGame:
5907           case IcsIdle:
5908           default:
5909             /* Extra move after we tried to stop.  The mode test is
5910                not a reliable way of detecting this problem, but it's
5911                the best we can do on engines that don't support ping.
5912             */
5913             if (appData.debugMode) {
5914                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5915                         cps->which, gameMode);
5916             }
5917             SendToProgram("undo\n", cps);
5918             return;
5919
5920           case MachinePlaysWhite:
5921           case IcsPlayingWhite:
5922             machineWhite = TRUE;
5923             break;
5924
5925           case MachinePlaysBlack:
5926           case IcsPlayingBlack:
5927             machineWhite = FALSE;
5928             break;
5929
5930           case TwoMachinesPlay:
5931             machineWhite = (cps->twoMachinesColor[0] == 'w');
5932             break;
5933         }
5934         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5935             if (appData.debugMode) {
5936                 fprintf(debugFP,
5937                         "Ignoring move out of turn by %s, gameMode %d"
5938                         ", forwardMost %d\n",
5939                         cps->which, gameMode, forwardMostMove);
5940             }
5941             return;
5942         }
5943
5944     if (appData.debugMode) { int f = forwardMostMove;
5945         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5946                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5947     }
5948         if(cps->alphaRank) AlphaRank(machineMove, 4);
5949         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5950                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5951             /* Machine move could not be parsed; ignore it. */
5952             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5953                     machineMove, cps->which);
5954             DisplayError(buf1, 0);
5955             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5956                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5957             if (gameMode == TwoMachinesPlay) {
5958               GameEnds(machineWhite ? BlackWins : WhiteWins,
5959                        buf1, GE_XBOARD);
5960             }
5961             return;
5962         }
5963
5964         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5965         /* So we have to redo legality test with true e.p. status here,  */
5966         /* to make sure an illegal e.p. capture does not slip through,   */
5967         /* to cause a forfeit on a justified illegal-move complaint      */
5968         /* of the opponent.                                              */
5969         if( gameMode==TwoMachinesPlay && appData.testLegality
5970             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5971                                                               ) {
5972            ChessMove moveType;
5973            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5974                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5975                              fromY, fromX, toY, toX, promoChar);
5976             if (appData.debugMode) {
5977                 int i;
5978                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5979                     castlingRights[forwardMostMove][i], castlingRank[i]);
5980                 fprintf(debugFP, "castling rights\n");
5981             }
5982             if(moveType == IllegalMove) {
5983                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5984                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5985                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5986                            buf1, GE_XBOARD);
5987                 return;
5988            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5989            /* [HGM] Kludge to handle engines that send FRC-style castling
5990               when they shouldn't (like TSCP-Gothic) */
5991            switch(moveType) {
5992              case WhiteASideCastleFR:
5993              case BlackASideCastleFR:
5994                toX+=2;
5995                currentMoveString[2]++;
5996                break;
5997              case WhiteHSideCastleFR:
5998              case BlackHSideCastleFR:
5999                toX--;
6000                currentMoveString[2]--;
6001                break;
6002              default: ; // nothing to do, but suppresses warning of pedantic compilers
6003            }
6004         }
6005         hintRequested = FALSE;
6006         lastHint[0] = NULLCHAR;
6007         bookRequested = FALSE;
6008         /* Program may be pondering now */
6009         cps->maybeThinking = TRUE;
6010         if (cps->sendTime == 2) cps->sendTime = 1;
6011         if (cps->offeredDraw) cps->offeredDraw--;
6012
6013 #if ZIPPY
6014         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6015             first.initDone) {
6016           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6017           ics_user_moved = 1;
6018           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6019                 char buf[3*MSG_SIZ];
6020
6021                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6022                         programStats.score / 100.,
6023                         programStats.depth,
6024                         programStats.time / 100.,
6025                         (unsigned int)programStats.nodes,
6026                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6027                         programStats.movelist);
6028                 SendToICS(buf);
6029 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6030           }
6031         }
6032 #endif
6033         /* currentMoveString is set as a side-effect of ParseOneMove */
6034         strcpy(machineMove, currentMoveString);
6035         strcat(machineMove, "\n");
6036         strcpy(moveList[forwardMostMove], machineMove);
6037
6038         /* [AS] Save move info and clear stats for next move */
6039         pvInfoList[ forwardMostMove ].score = programStats.score;
6040         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6041         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6042         ClearProgramStats();
6043         thinkOutput[0] = NULLCHAR;
6044         hiddenThinkOutputState = 0;
6045
6046         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6047
6048         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6049         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6050             int count = 0;
6051
6052             while( count < adjudicateLossPlies ) {
6053                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6054
6055                 if( count & 1 ) {
6056                     score = -score; /* Flip score for winning side */
6057                 }
6058
6059                 if( score > adjudicateLossThreshold ) {
6060                     break;
6061                 }
6062
6063                 count++;
6064             }
6065
6066             if( count >= adjudicateLossPlies ) {
6067                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6068
6069                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6070                     "Xboard adjudication", 
6071                     GE_XBOARD );
6072
6073                 return;
6074             }
6075         }
6076
6077         if( gameMode == TwoMachinesPlay ) {
6078           // [HGM] some adjudications useful with buggy engines
6079             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6080           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6081
6082
6083             if( appData.testLegality )
6084             {   /* [HGM] Some more adjudications for obstinate engines */
6085                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6086                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6087                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6088                 static int moveCount = 6;
6089                 ChessMove result;
6090                 char *reason = NULL;
6091
6092                 /* Count what is on board. */
6093                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6094                 {   ChessSquare p = boards[forwardMostMove][i][j];
6095                     int m=i;
6096
6097                     switch((int) p)
6098                     {   /* count B,N,R and other of each side */
6099                         case WhiteKing:
6100                         case BlackKing:
6101                              NrK++; break; // [HGM] atomic: count Kings
6102                         case WhiteKnight:
6103                              NrWN++; break;
6104                         case WhiteBishop:
6105                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6106                              bishopsColor |= 1 << ((i^j)&1);
6107                              NrWB++; break;
6108                         case BlackKnight:
6109                              NrBN++; break;
6110                         case BlackBishop:
6111                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6112                              bishopsColor |= 1 << ((i^j)&1);
6113                              NrBB++; break;
6114                         case WhiteRook:
6115                              NrWR++; break;
6116                         case BlackRook:
6117                              NrBR++; break;
6118                         case WhiteQueen:
6119                              NrWQ++; break;
6120                         case BlackQueen:
6121                              NrBQ++; break;
6122                         case EmptySquare: 
6123                              break;
6124                         case BlackPawn:
6125                              m = 7-i;
6126                         case WhitePawn:
6127                              PawnAdvance += m; NrPawns++;
6128                     }
6129                     NrPieces += (p != EmptySquare);
6130                     NrW += ((int)p < (int)BlackPawn);
6131                     if(gameInfo.variant == VariantXiangqi && 
6132                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6133                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6134                         NrW -= ((int)p < (int)BlackPawn);
6135                     }
6136                 }
6137
6138                 /* Some material-based adjudications that have to be made before stalemate test */
6139                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6140                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6141                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6142                      if(appData.checkMates) {
6143                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6144                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6145                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6146                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6147                          return;
6148                      }
6149                 }
6150
6151                 /* Bare King in Shatranj (loses) or Losers (wins) */
6152                 if( NrW == 1 || NrPieces - NrW == 1) {
6153                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6154                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6155                      if(appData.checkMates) {
6156                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6157                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6158                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6159                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6160                          return;
6161                      }
6162                   } else
6163                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6164                   {    /* bare King */
6165                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6166                         if(appData.checkMates) {
6167                             /* but only adjudicate if adjudication enabled */
6168                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6169                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6170                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6171                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6172                             return;
6173                         }
6174                   }
6175                 } else bare = 1;
6176
6177
6178             // don't wait for engine to announce game end if we can judge ourselves
6179             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6180                                        castlingRights[forwardMostMove]) ) {
6181               case MT_CHECK:
6182                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6183                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6184                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6185                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6186                             checkCnt++;
6187                         if(checkCnt >= 2) {
6188                             reason = "Xboard adjudication: 3rd check";
6189                             epStatus[forwardMostMove] = EP_CHECKMATE;
6190                             break;
6191                         }
6192                     }
6193                 }
6194               case MT_NONE:
6195               default:
6196                 break;
6197               case MT_STALEMATE:
6198               case MT_STAINMATE:
6199                 reason = "Xboard adjudication: Stalemate";
6200                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6201                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6202                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6203                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6204                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6205                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6206                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6207                                                                         EP_CHECKMATE : EP_WINS);
6208                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6209                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6210                 }
6211                 break;
6212               case MT_CHECKMATE:
6213                 reason = "Xboard adjudication: Checkmate";
6214                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6215                 break;
6216             }
6217
6218                 switch(i = epStatus[forwardMostMove]) {
6219                     case EP_STALEMATE:
6220                         result = GameIsDrawn; break;
6221                     case EP_CHECKMATE:
6222                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6223                     case EP_WINS:
6224                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6225                     default:
6226                         result = (ChessMove) 0;
6227                 }
6228                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6229                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6230                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6231                     GameEnds( result, reason, GE_XBOARD );
6232                     return;
6233                 }
6234
6235                 /* Next absolutely insufficient mating material. */
6236                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6237                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6238                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6239                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6240                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6241
6242                      /* always flag draws, for judging claims */
6243                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6244
6245                      if(appData.materialDraws) {
6246                          /* but only adjudicate them if adjudication enabled */
6247                          SendToProgram("force\n", cps->other); // suppress reply
6248                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6249                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6250                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6251                          return;
6252                      }
6253                 }
6254
6255                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6256                 if(NrPieces == 4 && 
6257                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6258                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6259                    || NrWN==2 || NrBN==2     /* KNNK */
6260                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6261                   ) ) {
6262                      if(--moveCount < 0 && appData.trivialDraws)
6263                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6264                           SendToProgram("force\n", cps->other); // suppress reply
6265                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6266                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6267                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6268                           return;
6269                      }
6270                 } else moveCount = 6;
6271             }
6272           }
6273
6274                 /* Check for rep-draws */
6275                 count = 0;
6276                 for(k = forwardMostMove-2;
6277                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6278                         epStatus[k] < EP_UNKNOWN &&
6279                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6280                     k-=2)
6281                 {   int rights=0;
6282                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6283                         /* compare castling rights */
6284                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6285                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6286                                 rights++; /* King lost rights, while rook still had them */
6287                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6288                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6289                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6290                                    rights++; /* but at least one rook lost them */
6291                         }
6292                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6293                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6294                                 rights++; 
6295                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6296                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6297                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6298                                    rights++;
6299                         }
6300                         if( rights == 0 && ++count > appData.drawRepeats-2
6301                             && appData.drawRepeats > 1) {
6302                              /* adjudicate after user-specified nr of repeats */
6303                              SendToProgram("force\n", cps->other); // suppress reply
6304                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6305                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6306                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6307                                 // [HGM] xiangqi: check for forbidden perpetuals
6308                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6309                                 for(m=forwardMostMove; m>k; m-=2) {
6310                                     if(MateTest(boards[m], PosFlags(m), 
6311                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6312                                         ourPerpetual = 0; // the current mover did not always check
6313                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6314                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6315                                         hisPerpetual = 0; // the opponent did not always check
6316                                 }
6317                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6318                                                                         ourPerpetual, hisPerpetual);
6319                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6320                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6321                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6322                                     return;
6323                                 }
6324                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6325                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6326                                 // Now check for perpetual chases
6327                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6328                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6329                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6330                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6331                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6332                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6333                                         return;
6334                                     }
6335                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6336                                         break; // Abort repetition-checking loop.
6337                                 }
6338                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6339                              }
6340                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6341                              return;
6342                         }
6343                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6344                              epStatus[forwardMostMove] = EP_REP_DRAW;
6345                     }
6346                 }
6347
6348                 /* Now we test for 50-move draws. Determine ply count */
6349                 count = forwardMostMove;
6350                 /* look for last irreversble move */
6351                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6352                     count--;
6353                 /* if we hit starting position, add initial plies */
6354                 if( count == backwardMostMove )
6355                     count -= initialRulePlies;
6356                 count = forwardMostMove - count; 
6357                 if( count >= 100)
6358                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6359                          /* this is used to judge if draw claims are legal */
6360                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6361                          SendToProgram("force\n", cps->other); // suppress reply
6362                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6363                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6364                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6365                          return;
6366                 }
6367
6368                 /* if draw offer is pending, treat it as a draw claim
6369                  * when draw condition present, to allow engines a way to
6370                  * claim draws before making their move to avoid a race
6371                  * condition occurring after their move
6372                  */
6373                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6374                          char *p = NULL;
6375                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6376                              p = "Draw claim: 50-move rule";
6377                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6378                              p = "Draw claim: 3-fold repetition";
6379                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6380                              p = "Draw claim: insufficient mating material";
6381                          if( p != NULL ) {
6382                              SendToProgram("force\n", cps->other); // suppress reply
6383                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6384                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6385                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6386                              return;
6387                          }
6388                 }
6389
6390
6391                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6392                     SendToProgram("force\n", cps->other); // suppress reply
6393                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6394                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6395
6396                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6397
6398                     return;
6399                 }
6400         }
6401
6402         bookHit = NULL;
6403         if (gameMode == TwoMachinesPlay) {
6404             /* [HGM] relaying draw offers moved to after reception of move */
6405             /* and interpreting offer as claim if it brings draw condition */
6406             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6407                 SendToProgram("draw\n", cps->other);
6408             }
6409             if (cps->other->sendTime) {
6410                 SendTimeRemaining(cps->other,
6411                                   cps->other->twoMachinesColor[0] == 'w');
6412             }
6413             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6414             if (firstMove && !bookHit) {
6415                 firstMove = FALSE;
6416                 if (cps->other->useColors) {
6417                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6418                 }
6419                 SendToProgram("go\n", cps->other);
6420             }
6421             cps->other->maybeThinking = TRUE;
6422         }
6423
6424         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6425         
6426         if (!pausing && appData.ringBellAfterMoves) {
6427             RingBell();
6428         }
6429
6430         /* 
6431          * Reenable menu items that were disabled while
6432          * machine was thinking
6433          */
6434         if (gameMode != TwoMachinesPlay)
6435             SetUserThinkingEnables();
6436
6437         // [HGM] book: after book hit opponent has received move and is now in force mode
6438         // force the book reply into it, and then fake that it outputted this move by jumping
6439         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6440         if(bookHit) {
6441                 static char bookMove[MSG_SIZ]; // a bit generous?
6442
6443                 strcpy(bookMove, "move ");
6444                 strcat(bookMove, bookHit);
6445                 message = bookMove;
6446                 cps = cps->other;
6447                 programStats.nodes = programStats.depth = programStats.time = 
6448                 programStats.score = programStats.got_only_move = 0;
6449                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6450
6451                 if(cps->lastPing != cps->lastPong) {
6452                     savedMessage = message; // args for deferred call
6453                     savedState = cps;
6454                     ScheduleDelayedEvent(DeferredBookMove, 10);
6455                     return;
6456                 }
6457                 goto FakeBookMove;
6458         }
6459
6460         return;
6461     }
6462
6463     /* Set special modes for chess engines.  Later something general
6464      *  could be added here; for now there is just one kludge feature,
6465      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6466      *  when "xboard" is given as an interactive command.
6467      */
6468     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6469         cps->useSigint = FALSE;
6470         cps->useSigterm = FALSE;
6471     }
6472     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6473       ParseFeatures(message+8, cps);
6474       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6475     }
6476
6477     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6478      * want this, I was asked to put it in, and obliged.
6479      */
6480     if (!strncmp(message, "setboard ", 9)) {
6481         Board initial_position; int i;
6482
6483         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6484
6485         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6486             DisplayError(_("Bad FEN received from engine"), 0);
6487             return ;
6488         } else {
6489            Reset(TRUE, FALSE);
6490            CopyBoard(boards[0], initial_position);
6491            initialRulePlies = FENrulePlies;
6492            epStatus[0] = FENepStatus;
6493            for( i=0; i<nrCastlingRights; i++ )
6494                 castlingRights[0][i] = FENcastlingRights[i];
6495            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6496            else gameMode = MachinePlaysBlack;                 
6497            DrawPosition(FALSE, boards[currentMove]);
6498         }
6499         return;
6500     }
6501
6502     /*
6503      * Look for communication commands
6504      */
6505     if (!strncmp(message, "telluser ", 9)) {
6506         DisplayNote(message + 9);
6507         return;
6508     }
6509     if (!strncmp(message, "tellusererror ", 14)) {
6510         DisplayError(message + 14, 0);
6511         return;
6512     }
6513     if (!strncmp(message, "tellopponent ", 13)) {
6514       if (appData.icsActive) {
6515         if (loggedOn) {
6516           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6517           SendToICS(buf1);
6518         }
6519       } else {
6520         DisplayNote(message + 13);
6521       }
6522       return;
6523     }
6524     if (!strncmp(message, "tellothers ", 11)) {
6525       if (appData.icsActive) {
6526         if (loggedOn) {
6527           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6528           SendToICS(buf1);
6529         }
6530       }
6531       return;
6532     }
6533     if (!strncmp(message, "tellall ", 8)) {
6534       if (appData.icsActive) {
6535         if (loggedOn) {
6536           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6537           SendToICS(buf1);
6538         }
6539       } else {
6540         DisplayNote(message + 8);
6541       }
6542       return;
6543     }
6544     if (strncmp(message, "warning", 7) == 0) {
6545         /* Undocumented feature, use tellusererror in new code */
6546         DisplayError(message, 0);
6547         return;
6548     }
6549     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6550         strcpy(realname, cps->tidy);
6551         strcat(realname, " query");
6552         AskQuestion(realname, buf2, buf1, cps->pr);
6553         return;
6554     }
6555     /* Commands from the engine directly to ICS.  We don't allow these to be 
6556      *  sent until we are logged on. Crafty kibitzes have been known to 
6557      *  interfere with the login process.
6558      */
6559     if (loggedOn) {
6560         if (!strncmp(message, "tellics ", 8)) {
6561             SendToICS(message + 8);
6562             SendToICS("\n");
6563             return;
6564         }
6565         if (!strncmp(message, "tellicsnoalias ", 15)) {
6566             SendToICS(ics_prefix);
6567             SendToICS(message + 15);
6568             SendToICS("\n");
6569             return;
6570         }
6571         /* The following are for backward compatibility only */
6572         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6573             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6574             SendToICS(ics_prefix);
6575             SendToICS(message);
6576             SendToICS("\n");
6577             return;
6578         }
6579     }
6580     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6581         return;
6582     }
6583     /*
6584      * If the move is illegal, cancel it and redraw the board.
6585      * Also deal with other error cases.  Matching is rather loose
6586      * here to accommodate engines written before the spec.
6587      */
6588     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6589         strncmp(message, "Error", 5) == 0) {
6590         if (StrStr(message, "name") || 
6591             StrStr(message, "rating") || StrStr(message, "?") ||
6592             StrStr(message, "result") || StrStr(message, "board") ||
6593             StrStr(message, "bk") || StrStr(message, "computer") ||
6594             StrStr(message, "variant") || StrStr(message, "hint") ||
6595             StrStr(message, "random") || StrStr(message, "depth") ||
6596             StrStr(message, "accepted")) {
6597             return;
6598         }
6599         if (StrStr(message, "protover")) {
6600           /* Program is responding to input, so it's apparently done
6601              initializing, and this error message indicates it is
6602              protocol version 1.  So we don't need to wait any longer
6603              for it to initialize and send feature commands. */
6604           FeatureDone(cps, 1);
6605           cps->protocolVersion = 1;
6606           return;
6607         }
6608         cps->maybeThinking = FALSE;
6609
6610         if (StrStr(message, "draw")) {
6611             /* Program doesn't have "draw" command */
6612             cps->sendDrawOffers = 0;
6613             return;
6614         }
6615         if (cps->sendTime != 1 &&
6616             (StrStr(message, "time") || StrStr(message, "otim"))) {
6617           /* Program apparently doesn't have "time" or "otim" command */
6618           cps->sendTime = 0;
6619           return;
6620         }
6621         if (StrStr(message, "analyze")) {
6622             cps->analysisSupport = FALSE;
6623             cps->analyzing = FALSE;
6624             Reset(FALSE, TRUE);
6625             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6626             DisplayError(buf2, 0);
6627             return;
6628         }
6629         if (StrStr(message, "(no matching move)st")) {
6630           /* Special kludge for GNU Chess 4 only */
6631           cps->stKludge = TRUE;
6632           SendTimeControl(cps, movesPerSession, timeControl,
6633                           timeIncrement, appData.searchDepth,
6634                           searchTime);
6635           return;
6636         }
6637         if (StrStr(message, "(no matching move)sd")) {
6638           /* Special kludge for GNU Chess 4 only */
6639           cps->sdKludge = TRUE;
6640           SendTimeControl(cps, movesPerSession, timeControl,
6641                           timeIncrement, appData.searchDepth,
6642                           searchTime);
6643           return;
6644         }
6645         if (!StrStr(message, "llegal")) {
6646             return;
6647         }
6648         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6649             gameMode == IcsIdle) return;
6650         if (forwardMostMove <= backwardMostMove) return;
6651         if (pausing) PauseEvent();
6652       if(appData.forceIllegal) {
6653             // [HGM] illegal: machine refused move; force position after move into it
6654           SendToProgram("force\n", cps);
6655           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6656                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6657                 // when black is to move, while there might be nothing on a2 or black
6658                 // might already have the move. So send the board as if white has the move.
6659                 // But first we must change the stm of the engine, as it refused the last move
6660                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6661                 if(WhiteOnMove(forwardMostMove)) {
6662                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6663                     SendBoard(cps, forwardMostMove); // kludgeless board
6664                 } else {
6665                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6666                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6667                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6668                 }
6669           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6670             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6671                  gameMode == TwoMachinesPlay)
6672               SendToProgram("go\n", cps);
6673             return;
6674       } else
6675         if (gameMode == PlayFromGameFile) {
6676             /* Stop reading this game file */
6677             gameMode = EditGame;
6678             ModeHighlight();
6679         }
6680         currentMove = forwardMostMove-1;
6681         DisplayMove(currentMove-1); /* before DisplayMoveError */
6682         SwitchClocks(forwardMostMove-1); // [HGM] race
6683         DisplayBothClocks();
6684         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6685                 parseList[currentMove], cps->which);
6686         DisplayMoveError(buf1);
6687         DrawPosition(FALSE, boards[currentMove]);
6688
6689         /* [HGM] illegal-move claim should forfeit game when Xboard */
6690         /* only passes fully legal moves                            */
6691         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6692             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6693                                 "False illegal-move claim", GE_XBOARD );
6694         }
6695         return;
6696     }
6697     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6698         /* Program has a broken "time" command that
6699            outputs a string not ending in newline.
6700            Don't use it. */
6701         cps->sendTime = 0;
6702     }
6703     
6704     /*
6705      * If chess program startup fails, exit with an error message.
6706      * Attempts to recover here are futile.
6707      */
6708     if ((StrStr(message, "unknown host") != NULL)
6709         || (StrStr(message, "No remote directory") != NULL)
6710         || (StrStr(message, "not found") != NULL)
6711         || (StrStr(message, "No such file") != NULL)
6712         || (StrStr(message, "can't alloc") != NULL)
6713         || (StrStr(message, "Permission denied") != NULL)) {
6714
6715         cps->maybeThinking = FALSE;
6716         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6717                 cps->which, cps->program, cps->host, message);
6718         RemoveInputSource(cps->isr);
6719         DisplayFatalError(buf1, 0, 1);
6720         return;
6721     }
6722     
6723     /* 
6724      * Look for hint output
6725      */
6726     if (sscanf(message, "Hint: %s", buf1) == 1) {
6727         if (cps == &first && hintRequested) {
6728             hintRequested = FALSE;
6729             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6730                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6731                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6732                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6733                                     fromY, fromX, toY, toX, promoChar, buf1);
6734                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6735                 DisplayInformation(buf2);
6736             } else {
6737                 /* Hint move could not be parsed!? */
6738               snprintf(buf2, sizeof(buf2),
6739                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6740                         buf1, cps->which);
6741                 DisplayError(buf2, 0);
6742             }
6743         } else {
6744             strcpy(lastHint, buf1);
6745         }
6746         return;
6747     }
6748
6749     /*
6750      * Ignore other messages if game is not in progress
6751      */
6752     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6753         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6754
6755     /*
6756      * look for win, lose, draw, or draw offer
6757      */
6758     if (strncmp(message, "1-0", 3) == 0) {
6759         char *p, *q, *r = "";
6760         p = strchr(message, '{');
6761         if (p) {
6762             q = strchr(p, '}');
6763             if (q) {
6764                 *q = NULLCHAR;
6765                 r = p + 1;
6766             }
6767         }
6768         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6769         return;
6770     } else if (strncmp(message, "0-1", 3) == 0) {
6771         char *p, *q, *r = "";
6772         p = strchr(message, '{');
6773         if (p) {
6774             q = strchr(p, '}');
6775             if (q) {
6776                 *q = NULLCHAR;
6777                 r = p + 1;
6778             }
6779         }
6780         /* Kludge for Arasan 4.1 bug */
6781         if (strcmp(r, "Black resigns") == 0) {
6782             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6783             return;
6784         }
6785         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6786         return;
6787     } else if (strncmp(message, "1/2", 3) == 0) {
6788         char *p, *q, *r = "";
6789         p = strchr(message, '{');
6790         if (p) {
6791             q = strchr(p, '}');
6792             if (q) {
6793                 *q = NULLCHAR;
6794                 r = p + 1;
6795             }
6796         }
6797             
6798         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6799         return;
6800
6801     } else if (strncmp(message, "White resign", 12) == 0) {
6802         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6803         return;
6804     } else if (strncmp(message, "Black resign", 12) == 0) {
6805         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6806         return;
6807     } else if (strncmp(message, "White matches", 13) == 0 ||
6808                strncmp(message, "Black matches", 13) == 0   ) {
6809         /* [HGM] ignore GNUShogi noises */
6810         return;
6811     } else if (strncmp(message, "White", 5) == 0 &&
6812                message[5] != '(' &&
6813                StrStr(message, "Black") == NULL) {
6814         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6815         return;
6816     } else if (strncmp(message, "Black", 5) == 0 &&
6817                message[5] != '(') {
6818         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6819         return;
6820     } else if (strcmp(message, "resign") == 0 ||
6821                strcmp(message, "computer resigns") == 0) {
6822         switch (gameMode) {
6823           case MachinePlaysBlack:
6824           case IcsPlayingBlack:
6825             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6826             break;
6827           case MachinePlaysWhite:
6828           case IcsPlayingWhite:
6829             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6830             break;
6831           case TwoMachinesPlay:
6832             if (cps->twoMachinesColor[0] == 'w')
6833               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6834             else
6835               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6836             break;
6837           default:
6838             /* can't happen */
6839             break;
6840         }
6841         return;
6842     } else if (strncmp(message, "opponent mates", 14) == 0) {
6843         switch (gameMode) {
6844           case MachinePlaysBlack:
6845           case IcsPlayingBlack:
6846             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6847             break;
6848           case MachinePlaysWhite:
6849           case IcsPlayingWhite:
6850             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6851             break;
6852           case TwoMachinesPlay:
6853             if (cps->twoMachinesColor[0] == 'w')
6854               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6855             else
6856               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6857             break;
6858           default:
6859             /* can't happen */
6860             break;
6861         }
6862         return;
6863     } else if (strncmp(message, "computer mates", 14) == 0) {
6864         switch (gameMode) {
6865           case MachinePlaysBlack:
6866           case IcsPlayingBlack:
6867             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6868             break;
6869           case MachinePlaysWhite:
6870           case IcsPlayingWhite:
6871             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6872             break;
6873           case TwoMachinesPlay:
6874             if (cps->twoMachinesColor[0] == 'w')
6875               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6876             else
6877               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6878             break;
6879           default:
6880             /* can't happen */
6881             break;
6882         }
6883         return;
6884     } else if (strncmp(message, "checkmate", 9) == 0) {
6885         if (WhiteOnMove(forwardMostMove)) {
6886             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6887         } else {
6888             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6889         }
6890         return;
6891     } else if (strstr(message, "Draw") != NULL ||
6892                strstr(message, "game is a draw") != NULL) {
6893         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6894         return;
6895     } else if (strstr(message, "offer") != NULL &&
6896                strstr(message, "draw") != NULL) {
6897 #if ZIPPY
6898         if (appData.zippyPlay && first.initDone) {
6899             /* Relay offer to ICS */
6900             SendToICS(ics_prefix);
6901             SendToICS("draw\n");
6902         }
6903 #endif
6904         cps->offeredDraw = 2; /* valid until this engine moves twice */
6905         if (gameMode == TwoMachinesPlay) {
6906             if (cps->other->offeredDraw) {
6907                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6908             /* [HGM] in two-machine mode we delay relaying draw offer      */
6909             /* until after we also have move, to see if it is really claim */
6910             }
6911         } else if (gameMode == MachinePlaysWhite ||
6912                    gameMode == MachinePlaysBlack) {
6913           if (userOfferedDraw) {
6914             DisplayInformation(_("Machine accepts your draw offer"));
6915             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6916           } else {
6917             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6918           }
6919         }
6920     }
6921
6922     
6923     /*
6924      * Look for thinking output
6925      */
6926     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6927           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6928                                 ) {
6929         int plylev, mvleft, mvtot, curscore, time;
6930         char mvname[MOVE_LEN];
6931         u64 nodes; // [DM]
6932         char plyext;
6933         int ignore = FALSE;
6934         int prefixHint = FALSE;
6935         mvname[0] = NULLCHAR;
6936
6937         switch (gameMode) {
6938           case MachinePlaysBlack:
6939           case IcsPlayingBlack:
6940             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6941             break;
6942           case MachinePlaysWhite:
6943           case IcsPlayingWhite:
6944             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6945             break;
6946           case AnalyzeMode:
6947           case AnalyzeFile:
6948             break;
6949           case IcsObserving: /* [DM] icsEngineAnalyze */
6950             if (!appData.icsEngineAnalyze) ignore = TRUE;
6951             break;
6952           case TwoMachinesPlay:
6953             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6954                 ignore = TRUE;
6955             }
6956             break;
6957           default:
6958             ignore = TRUE;
6959             break;
6960         }
6961
6962         if (!ignore) {
6963             buf1[0] = NULLCHAR;
6964             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6965                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6966
6967                 if (plyext != ' ' && plyext != '\t') {
6968                     time *= 100;
6969                 }
6970
6971                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6972                 if( cps->scoreIsAbsolute && 
6973                     ( gameMode == MachinePlaysBlack ||
6974                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6975                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
6976                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6977                      !WhiteOnMove(currentMove)
6978                     ) )
6979                 {
6980                     curscore = -curscore;
6981                 }
6982
6983
6984                 programStats.depth = plylev;
6985                 programStats.nodes = nodes;
6986                 programStats.time = time;
6987                 programStats.score = curscore;
6988                 programStats.got_only_move = 0;
6989
6990                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6991                         int ticklen;
6992
6993                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6994                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6995                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6996                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6997                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6998                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6999                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7000                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7001                 }
7002
7003                 /* Buffer overflow protection */
7004                 if (buf1[0] != NULLCHAR) {
7005                     if (strlen(buf1) >= sizeof(programStats.movelist)
7006                         && appData.debugMode) {
7007                         fprintf(debugFP,
7008                                 "PV is too long; using the first %u bytes.\n",
7009                                 (unsigned) sizeof(programStats.movelist) - 1);
7010                     }
7011
7012                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7013                 } else {
7014                     sprintf(programStats.movelist, " no PV\n");
7015                 }
7016
7017                 if (programStats.seen_stat) {
7018                     programStats.ok_to_send = 1;
7019                 }
7020
7021                 if (strchr(programStats.movelist, '(') != NULL) {
7022                     programStats.line_is_book = 1;
7023                     programStats.nr_moves = 0;
7024                     programStats.moves_left = 0;
7025                 } else {
7026                     programStats.line_is_book = 0;
7027                 }
7028
7029                 SendProgramStatsToFrontend( cps, &programStats );
7030
7031                 /* 
7032                     [AS] Protect the thinkOutput buffer from overflow... this
7033                     is only useful if buf1 hasn't overflowed first!
7034                 */
7035                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7036                         plylev, 
7037                         (gameMode == TwoMachinesPlay ?
7038                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7039                         ((double) curscore) / 100.0,
7040                         prefixHint ? lastHint : "",
7041                         prefixHint ? " " : "" );
7042
7043                 if( buf1[0] != NULLCHAR ) {
7044                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7045
7046                     if( strlen(buf1) > max_len ) {
7047                         if( appData.debugMode) {
7048                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7049                         }
7050                         buf1[max_len+1] = '\0';
7051                     }
7052
7053                     strcat( thinkOutput, buf1 );
7054                 }
7055
7056                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7057                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7058                     DisplayMove(currentMove - 1);
7059                 }
7060                 return;
7061
7062             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7063                 /* crafty (9.25+) says "(only move) <move>"
7064                  * if there is only 1 legal move
7065                  */
7066                 sscanf(p, "(only move) %s", buf1);
7067                 sprintf(thinkOutput, "%s (only move)", buf1);
7068                 sprintf(programStats.movelist, "%s (only move)", buf1);
7069                 programStats.depth = 1;
7070                 programStats.nr_moves = 1;
7071                 programStats.moves_left = 1;
7072                 programStats.nodes = 1;
7073                 programStats.time = 1;
7074                 programStats.got_only_move = 1;
7075
7076                 /* Not really, but we also use this member to
7077                    mean "line isn't going to change" (Crafty
7078                    isn't searching, so stats won't change) */
7079                 programStats.line_is_book = 1;
7080
7081                 SendProgramStatsToFrontend( cps, &programStats );
7082                 
7083                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7084                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7085                     DisplayMove(currentMove - 1);
7086                 }
7087                 return;
7088             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7089                               &time, &nodes, &plylev, &mvleft,
7090                               &mvtot, mvname) >= 5) {
7091                 /* The stat01: line is from Crafty (9.29+) in response
7092                    to the "." command */
7093                 programStats.seen_stat = 1;
7094                 cps->maybeThinking = TRUE;
7095
7096                 if (programStats.got_only_move || !appData.periodicUpdates)
7097                   return;
7098
7099                 programStats.depth = plylev;
7100                 programStats.time = time;
7101                 programStats.nodes = nodes;
7102                 programStats.moves_left = mvleft;
7103                 programStats.nr_moves = mvtot;
7104                 strcpy(programStats.move_name, mvname);
7105                 programStats.ok_to_send = 1;
7106                 programStats.movelist[0] = '\0';
7107
7108                 SendProgramStatsToFrontend( cps, &programStats );
7109
7110                 return;
7111
7112             } else if (strncmp(message,"++",2) == 0) {
7113                 /* Crafty 9.29+ outputs this */
7114                 programStats.got_fail = 2;
7115                 return;
7116
7117             } else if (strncmp(message,"--",2) == 0) {
7118                 /* Crafty 9.29+ outputs this */
7119                 programStats.got_fail = 1;
7120                 return;
7121
7122             } else if (thinkOutput[0] != NULLCHAR &&
7123                        strncmp(message, "    ", 4) == 0) {
7124                 unsigned message_len;
7125
7126                 p = message;
7127                 while (*p && *p == ' ') p++;
7128
7129                 message_len = strlen( p );
7130
7131                 /* [AS] Avoid buffer overflow */
7132                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7133                     strcat(thinkOutput, " ");
7134                     strcat(thinkOutput, p);
7135                 }
7136
7137                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7138                     strcat(programStats.movelist, " ");
7139                     strcat(programStats.movelist, p);
7140                 }
7141
7142                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7143                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7144                     DisplayMove(currentMove - 1);
7145                 }
7146                 return;
7147             }
7148         }
7149         else {
7150             buf1[0] = NULLCHAR;
7151
7152             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7153                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7154             {
7155                 ChessProgramStats cpstats;
7156
7157                 if (plyext != ' ' && plyext != '\t') {
7158                     time *= 100;
7159                 }
7160
7161                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7162                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7163                     curscore = -curscore;
7164                 }
7165
7166                 cpstats.depth = plylev;
7167                 cpstats.nodes = nodes;
7168                 cpstats.time = time;
7169                 cpstats.score = curscore;
7170                 cpstats.got_only_move = 0;
7171                 cpstats.movelist[0] = '\0';
7172
7173                 if (buf1[0] != NULLCHAR) {
7174                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7175                 }
7176
7177                 cpstats.ok_to_send = 0;
7178                 cpstats.line_is_book = 0;
7179                 cpstats.nr_moves = 0;
7180                 cpstats.moves_left = 0;
7181
7182                 SendProgramStatsToFrontend( cps, &cpstats );
7183             }
7184         }
7185     }
7186 }
7187
7188
7189 /* Parse a game score from the character string "game", and
7190    record it as the history of the current game.  The game
7191    score is NOT assumed to start from the standard position. 
7192    The display is not updated in any way.
7193    */
7194 void
7195 ParseGameHistory(game)
7196      char *game;
7197 {
7198     ChessMove moveType;
7199     int fromX, fromY, toX, toY, boardIndex;
7200     char promoChar;
7201     char *p, *q;
7202     char buf[MSG_SIZ];
7203
7204     if (appData.debugMode)
7205       fprintf(debugFP, "Parsing game history: %s\n", game);
7206
7207     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7208     gameInfo.site = StrSave(appData.icsHost);
7209     gameInfo.date = PGNDate();
7210     gameInfo.round = StrSave("-");
7211
7212     /* Parse out names of players */
7213     while (*game == ' ') game++;
7214     p = buf;
7215     while (*game != ' ') *p++ = *game++;
7216     *p = NULLCHAR;
7217     gameInfo.white = StrSave(buf);
7218     while (*game == ' ') game++;
7219     p = buf;
7220     while (*game != ' ' && *game != '\n') *p++ = *game++;
7221     *p = NULLCHAR;
7222     gameInfo.black = StrSave(buf);
7223
7224     /* Parse moves */
7225     boardIndex = blackPlaysFirst ? 1 : 0;
7226     yynewstr(game);
7227     for (;;) {
7228         yyboardindex = boardIndex;
7229         moveType = (ChessMove) yylex();
7230         switch (moveType) {
7231           case IllegalMove:             /* maybe suicide chess, etc. */
7232   if (appData.debugMode) {
7233     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7234     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7235     setbuf(debugFP, NULL);
7236   }
7237           case WhitePromotionChancellor:
7238           case BlackPromotionChancellor:
7239           case WhitePromotionArchbishop:
7240           case BlackPromotionArchbishop:
7241           case WhitePromotionQueen:
7242           case BlackPromotionQueen:
7243           case WhitePromotionRook:
7244           case BlackPromotionRook:
7245           case WhitePromotionBishop:
7246           case BlackPromotionBishop:
7247           case WhitePromotionKnight:
7248           case BlackPromotionKnight:
7249           case WhitePromotionKing:
7250           case BlackPromotionKing:
7251           case NormalMove:
7252           case WhiteCapturesEnPassant:
7253           case BlackCapturesEnPassant:
7254           case WhiteKingSideCastle:
7255           case WhiteQueenSideCastle:
7256           case BlackKingSideCastle:
7257           case BlackQueenSideCastle:
7258           case WhiteKingSideCastleWild:
7259           case WhiteQueenSideCastleWild:
7260           case BlackKingSideCastleWild:
7261           case BlackQueenSideCastleWild:
7262           /* PUSH Fabien */
7263           case WhiteHSideCastleFR:
7264           case WhiteASideCastleFR:
7265           case BlackHSideCastleFR:
7266           case BlackASideCastleFR:
7267           /* POP Fabien */
7268             fromX = currentMoveString[0] - AAA;
7269             fromY = currentMoveString[1] - ONE;
7270             toX = currentMoveString[2] - AAA;
7271             toY = currentMoveString[3] - ONE;
7272             promoChar = currentMoveString[4];
7273             break;
7274           case WhiteDrop:
7275           case BlackDrop:
7276             fromX = moveType == WhiteDrop ?
7277               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7278             (int) CharToPiece(ToLower(currentMoveString[0]));
7279             fromY = DROP_RANK;
7280             toX = currentMoveString[2] - AAA;
7281             toY = currentMoveString[3] - ONE;
7282             promoChar = NULLCHAR;
7283             break;
7284           case AmbiguousMove:
7285             /* bug? */
7286             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7287   if (appData.debugMode) {
7288     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7289     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7290     setbuf(debugFP, NULL);
7291   }
7292             DisplayError(buf, 0);
7293             return;
7294           case ImpossibleMove:
7295             /* bug? */
7296             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7297   if (appData.debugMode) {
7298     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7299     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7300     setbuf(debugFP, NULL);
7301   }
7302             DisplayError(buf, 0);
7303             return;
7304           case (ChessMove) 0:   /* end of file */
7305             if (boardIndex < backwardMostMove) {
7306                 /* Oops, gap.  How did that happen? */
7307                 DisplayError(_("Gap in move list"), 0);
7308                 return;
7309             }
7310             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7311             if (boardIndex > forwardMostMove) {
7312                 forwardMostMove = boardIndex;
7313             }
7314             return;
7315           case ElapsedTime:
7316             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7317                 strcat(parseList[boardIndex-1], " ");
7318                 strcat(parseList[boardIndex-1], yy_text);
7319             }
7320             continue;
7321           case Comment:
7322           case PGNTag:
7323           case NAG:
7324           default:
7325             /* ignore */
7326             continue;
7327           case WhiteWins:
7328           case BlackWins:
7329           case GameIsDrawn:
7330           case GameUnfinished:
7331             if (gameMode == IcsExamining) {
7332                 if (boardIndex < backwardMostMove) {
7333                     /* Oops, gap.  How did that happen? */
7334                     return;
7335                 }
7336                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7337                 return;
7338             }
7339             gameInfo.result = moveType;
7340             p = strchr(yy_text, '{');
7341             if (p == NULL) p = strchr(yy_text, '(');
7342             if (p == NULL) {
7343                 p = yy_text;
7344                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7345             } else {
7346                 q = strchr(p, *p == '{' ? '}' : ')');
7347                 if (q != NULL) *q = NULLCHAR;
7348                 p++;
7349             }
7350             gameInfo.resultDetails = StrSave(p);
7351             continue;
7352         }
7353         if (boardIndex >= forwardMostMove &&
7354             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7355             backwardMostMove = blackPlaysFirst ? 1 : 0;
7356             return;
7357         }
7358         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7359                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7360                                  parseList[boardIndex]);
7361         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7362         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7363         /* currentMoveString is set as a side-effect of yylex */
7364         strcpy(moveList[boardIndex], currentMoveString);
7365         strcat(moveList[boardIndex], "\n");
7366         boardIndex++;
7367         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7368                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7369         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7370                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7371           case MT_NONE:
7372           case MT_STALEMATE:
7373           default:
7374             break;
7375           case MT_CHECK:
7376             if(gameInfo.variant != VariantShogi)
7377                 strcat(parseList[boardIndex - 1], "+");
7378             break;
7379           case MT_CHECKMATE:
7380           case MT_STAINMATE:
7381             strcat(parseList[boardIndex - 1], "#");
7382             break;
7383         }
7384     }
7385 }
7386
7387
7388 /* Apply a move to the given board  */
7389 void
7390 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7391      int fromX, fromY, toX, toY;
7392      int promoChar;
7393      Board board;
7394      char *castling;
7395      char *ep;
7396 {
7397   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7398   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7399
7400     /* [HGM] compute & store e.p. status and castling rights for new position */
7401     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7402     { int i;
7403
7404       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7405       oldEP = *ep;
7406       *ep = EP_NONE;
7407
7408       if( board[toY][toX] != EmptySquare ) 
7409            *ep = EP_CAPTURE;  
7410
7411       if( board[fromY][fromX] == WhitePawn ) {
7412            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7413                *ep = EP_PAWN_MOVE;
7414            if( toY-fromY==2) {
7415                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7416                         gameInfo.variant != VariantBerolina || toX < fromX)
7417                       *ep = toX | berolina;
7418                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7419                         gameInfo.variant != VariantBerolina || toX > fromX) 
7420                       *ep = toX;
7421            }
7422       } else 
7423       if( board[fromY][fromX] == BlackPawn ) {
7424            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7425                *ep = EP_PAWN_MOVE; 
7426            if( toY-fromY== -2) {
7427                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7428                         gameInfo.variant != VariantBerolina || toX < fromX)
7429                       *ep = toX | berolina;
7430                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7431                         gameInfo.variant != VariantBerolina || toX > fromX) 
7432                       *ep = toX;
7433            }
7434        }
7435
7436        for(i=0; i<nrCastlingRights; i++) {
7437            if(castling[i] == fromX && castlingRank[i] == fromY ||
7438               castling[i] == toX   && castlingRank[i] == toY   
7439              ) castling[i] = -1; // revoke for moved or captured piece
7440        }
7441
7442     }
7443
7444   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7445   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7446        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7447          
7448   if (fromX == toX && fromY == toY) return;
7449
7450   if (fromY == DROP_RANK) {
7451         /* must be first */
7452         piece = board[toY][toX] = (ChessSquare) fromX;
7453   } else {
7454      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7455      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7456      if(gameInfo.variant == VariantKnightmate)
7457          king += (int) WhiteUnicorn - (int) WhiteKing;
7458
7459     /* Code added by Tord: */
7460     /* FRC castling assumed when king captures friendly rook. */
7461     if (board[fromY][fromX] == WhiteKing &&
7462              board[toY][toX] == WhiteRook) {
7463       board[fromY][fromX] = EmptySquare;
7464       board[toY][toX] = EmptySquare;
7465       if(toX > fromX) {
7466         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7467       } else {
7468         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7469       }
7470     } else if (board[fromY][fromX] == BlackKing &&
7471                board[toY][toX] == BlackRook) {
7472       board[fromY][fromX] = EmptySquare;
7473       board[toY][toX] = EmptySquare;
7474       if(toX > fromX) {
7475         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7476       } else {
7477         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7478       }
7479     /* End of code added by Tord */
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 (board[fromY][fromX] == WhitePawn
7496                && toY >= BOARD_HEIGHT-promoRank
7497                && gameInfo.variant != VariantXiangqi
7498                ) {
7499         /* white pawn promotion */
7500         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7501         if (board[toY][toX] == EmptySquare) {
7502             board[toY][toX] = WhiteQueen;
7503         }
7504         if(gameInfo.variant==VariantBughouse ||
7505            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7506             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7507         board[fromY][fromX] = EmptySquare;
7508     } else if ((fromY == BOARD_HEIGHT-4)
7509                && (toX != fromX)
7510                && gameInfo.variant != VariantXiangqi
7511                && gameInfo.variant != VariantBerolina
7512                && (board[fromY][fromX] == WhitePawn)
7513                && (board[toY][toX] == EmptySquare)) {
7514         board[fromY][fromX] = EmptySquare;
7515         board[toY][toX] = WhitePawn;
7516         captured = board[toY - 1][toX];
7517         board[toY - 1][toX] = EmptySquare;
7518     } else if ((fromY == BOARD_HEIGHT-4)
7519                && (toX == fromX)
7520                && gameInfo.variant == VariantBerolina
7521                && (board[fromY][fromX] == WhitePawn)
7522                && (board[toY][toX] == EmptySquare)) {
7523         board[fromY][fromX] = EmptySquare;
7524         board[toY][toX] = WhitePawn;
7525         if(oldEP & EP_BEROLIN_A) {
7526                 captured = board[fromY][fromX-1];
7527                 board[fromY][fromX-1] = EmptySquare;
7528         }else{  captured = board[fromY][fromX+1];
7529                 board[fromY][fromX+1] = EmptySquare;
7530         }
7531     } else if (board[fromY][fromX] == king
7532         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7533                && toY == fromY && toX > fromX+1) {
7534         board[fromY][fromX] = EmptySquare;
7535         board[toY][toX] = king;
7536         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7537         board[fromY][BOARD_RGHT-1] = EmptySquare;
7538     } else if (board[fromY][fromX] == king
7539         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7540                && toY == fromY && toX < fromX-1) {
7541         board[fromY][fromX] = EmptySquare;
7542         board[toY][toX] = king;
7543         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7544         board[fromY][BOARD_LEFT] = EmptySquare;
7545     } else if (fromY == 7 && fromX == 3
7546                && board[fromY][fromX] == BlackKing
7547                && toY == 7 && toX == 5) {
7548         board[fromY][fromX] = EmptySquare;
7549         board[toY][toX] = BlackKing;
7550         board[fromY][7] = EmptySquare;
7551         board[toY][4] = BlackRook;
7552     } else if (fromY == 7 && fromX == 3
7553                && board[fromY][fromX] == BlackKing
7554                && toY == 7 && toX == 1) {
7555         board[fromY][fromX] = EmptySquare;
7556         board[toY][toX] = BlackKing;
7557         board[fromY][0] = EmptySquare;
7558         board[toY][2] = BlackRook;
7559     } else if (board[fromY][fromX] == BlackPawn
7560                && toY < promoRank
7561                && gameInfo.variant != VariantXiangqi
7562                ) {
7563         /* black pawn promotion */
7564         board[toY][toX] = CharToPiece(ToLower(promoChar));
7565         if (board[toY][toX] == EmptySquare) {
7566             board[toY][toX] = BlackQueen;
7567         }
7568         if(gameInfo.variant==VariantBughouse ||
7569            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7570             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7571         board[fromY][fromX] = EmptySquare;
7572     } else if ((fromY == 3)
7573                && (toX != fromX)
7574                && gameInfo.variant != VariantXiangqi
7575                && gameInfo.variant != VariantBerolina
7576                && (board[fromY][fromX] == BlackPawn)
7577                && (board[toY][toX] == EmptySquare)) {
7578         board[fromY][fromX] = EmptySquare;
7579         board[toY][toX] = BlackPawn;
7580         captured = board[toY + 1][toX];
7581         board[toY + 1][toX] = EmptySquare;
7582     } else if ((fromY == 3)
7583                && (toX == fromX)
7584                && gameInfo.variant == VariantBerolina
7585                && (board[fromY][fromX] == BlackPawn)
7586                && (board[toY][toX] == EmptySquare)) {
7587         board[fromY][fromX] = EmptySquare;
7588         board[toY][toX] = BlackPawn;
7589         if(oldEP & EP_BEROLIN_A) {
7590                 captured = board[fromY][fromX-1];
7591                 board[fromY][fromX-1] = EmptySquare;
7592         }else{  captured = board[fromY][fromX+1];
7593                 board[fromY][fromX+1] = EmptySquare;
7594         }
7595     } else {
7596         board[toY][toX] = board[fromY][fromX];
7597         board[fromY][fromX] = EmptySquare;
7598     }
7599
7600     /* [HGM] now we promote for Shogi, if needed */
7601     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7602         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7603   }
7604
7605     if (gameInfo.holdingsWidth != 0) {
7606
7607       /* !!A lot more code needs to be written to support holdings  */
7608       /* [HGM] OK, so I have written it. Holdings are stored in the */
7609       /* penultimate board files, so they are automaticlly stored   */
7610       /* in the game history.                                       */
7611       if (fromY == DROP_RANK) {
7612         /* Delete from holdings, by decreasing count */
7613         /* and erasing image if necessary            */
7614         p = (int) fromX;
7615         if(p < (int) BlackPawn) { /* white drop */
7616              p -= (int)WhitePawn;
7617                  p = PieceToNumber((ChessSquare)p);
7618              if(p >= gameInfo.holdingsSize) p = 0;
7619              if(--board[p][BOARD_WIDTH-2] <= 0)
7620                   board[p][BOARD_WIDTH-1] = EmptySquare;
7621              if((int)board[p][BOARD_WIDTH-2] < 0)
7622                         board[p][BOARD_WIDTH-2] = 0;
7623         } else {                  /* black drop */
7624              p -= (int)BlackPawn;
7625                  p = PieceToNumber((ChessSquare)p);
7626              if(p >= gameInfo.holdingsSize) p = 0;
7627              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7628                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7629              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7630                         board[BOARD_HEIGHT-1-p][1] = 0;
7631         }
7632       }
7633       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7634           && gameInfo.variant != VariantBughouse        ) {
7635         /* [HGM] holdings: Add to holdings, if holdings exist */
7636         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7637                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7638                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7639         }
7640         p = (int) captured;
7641         if (p >= (int) BlackPawn) {
7642           p -= (int)BlackPawn;
7643           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7644                   /* in Shogi restore piece to its original  first */
7645                   captured = (ChessSquare) (DEMOTED captured);
7646                   p = DEMOTED p;
7647           }
7648           p = PieceToNumber((ChessSquare)p);
7649           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7650           board[p][BOARD_WIDTH-2]++;
7651           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7652         } else {
7653           p -= (int)WhitePawn;
7654           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7655                   captured = (ChessSquare) (DEMOTED captured);
7656                   p = DEMOTED p;
7657           }
7658           p = PieceToNumber((ChessSquare)p);
7659           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7660           board[BOARD_HEIGHT-1-p][1]++;
7661           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7662         }
7663       }
7664     } else if (gameInfo.variant == VariantAtomic) {
7665       if (captured != EmptySquare) {
7666         int y, x;
7667         for (y = toY-1; y <= toY+1; y++) {
7668           for (x = toX-1; x <= toX+1; x++) {
7669             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7670                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7671               board[y][x] = EmptySquare;
7672             }
7673           }
7674         }
7675         board[toY][toX] = EmptySquare;
7676       }
7677     }
7678     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7679         /* [HGM] Shogi promotions */
7680         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7681     }
7682
7683     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7684                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7685         // [HGM] superchess: take promotion piece out of holdings
7686         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7687         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7688             if(!--board[k][BOARD_WIDTH-2])
7689                 board[k][BOARD_WIDTH-1] = EmptySquare;
7690         } else {
7691             if(!--board[BOARD_HEIGHT-1-k][1])
7692                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7693         }
7694     }
7695
7696 }
7697
7698 /* Updates forwardMostMove */
7699 void
7700 MakeMove(fromX, fromY, toX, toY, promoChar)
7701      int fromX, fromY, toX, toY;
7702      int promoChar;
7703 {
7704 //    forwardMostMove++; // [HGM] bare: moved downstream
7705
7706     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7707         int timeLeft; static int lastLoadFlag=0; int king, piece;
7708         piece = boards[forwardMostMove][fromY][fromX];
7709         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7710         if(gameInfo.variant == VariantKnightmate)
7711             king += (int) WhiteUnicorn - (int) WhiteKing;
7712         if(forwardMostMove == 0) {
7713             if(blackPlaysFirst) 
7714                 fprintf(serverMoves, "%s;", second.tidy);
7715             fprintf(serverMoves, "%s;", first.tidy);
7716             if(!blackPlaysFirst) 
7717                 fprintf(serverMoves, "%s;", second.tidy);
7718         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7719         lastLoadFlag = loadFlag;
7720         // print base move
7721         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7722         // print castling suffix
7723         if( toY == fromY && piece == king ) {
7724             if(toX-fromX > 1)
7725                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7726             if(fromX-toX >1)
7727                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7728         }
7729         // e.p. suffix
7730         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7731              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7732              boards[forwardMostMove][toY][toX] == EmptySquare
7733              && fromX != toX )
7734                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7735         // promotion suffix
7736         if(promoChar != NULLCHAR)
7737                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7738         if(!loadFlag) {
7739             fprintf(serverMoves, "/%d/%d",
7740                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7741             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7742             else                      timeLeft = blackTimeRemaining/1000;
7743             fprintf(serverMoves, "/%d", timeLeft);
7744         }
7745         fflush(serverMoves);
7746     }
7747
7748     if (forwardMostMove+1 >= MAX_MOVES) {
7749       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7750                         0, 1);
7751       return;
7752     }
7753     if (commentList[forwardMostMove+1] != NULL) {
7754         free(commentList[forwardMostMove+1]);
7755         commentList[forwardMostMove+1] = NULL;
7756     }
7757     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7758     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7759     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7760                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7761     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7762     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
7763     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7764     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7765     gameInfo.result = GameUnfinished;
7766     if (gameInfo.resultDetails != NULL) {
7767         free(gameInfo.resultDetails);
7768         gameInfo.resultDetails = NULL;
7769     }
7770     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7771                               moveList[forwardMostMove - 1]);
7772     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7773                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7774                              fromY, fromX, toY, toX, promoChar,
7775                              parseList[forwardMostMove - 1]);
7776     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7777                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7778                             castlingRights[forwardMostMove]) ) {
7779       case MT_NONE:
7780       case MT_STALEMATE:
7781       default:
7782         break;
7783       case MT_CHECK:
7784         if(gameInfo.variant != VariantShogi)
7785             strcat(parseList[forwardMostMove - 1], "+");
7786         break;
7787       case MT_CHECKMATE:
7788       case MT_STAINMATE:
7789         strcat(parseList[forwardMostMove - 1], "#");
7790         break;
7791     }
7792     if (appData.debugMode) {
7793         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7794     }
7795
7796 }
7797
7798 /* Updates currentMove if not pausing */
7799 void
7800 ShowMove(fromX, fromY, toX, toY)
7801 {
7802     int instant = (gameMode == PlayFromGameFile) ?
7803         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7804     if(appData.noGUI) return;
7805     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7806         if (!instant) {
7807             if (forwardMostMove == currentMove + 1) {
7808                 AnimateMove(boards[forwardMostMove - 1],
7809                             fromX, fromY, toX, toY);
7810             }
7811             if (appData.highlightLastMove) {
7812                 SetHighlights(fromX, fromY, toX, toY);
7813             }
7814         }
7815         currentMove = forwardMostMove;
7816     }
7817
7818     if (instant) return;
7819
7820     DisplayMove(currentMove - 1);
7821     DrawPosition(FALSE, boards[currentMove]);
7822     DisplayBothClocks();
7823     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7824 }
7825
7826 void SendEgtPath(ChessProgramState *cps)
7827 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7828         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7829
7830         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7831
7832         while(*p) {
7833             char c, *q = name+1, *r, *s;
7834
7835             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7836             while(*p && *p != ',') *q++ = *p++;
7837             *q++ = ':'; *q = 0;
7838             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7839                 strcmp(name, ",nalimov:") == 0 ) {
7840                 // take nalimov path from the menu-changeable option first, if it is defined
7841                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7842                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7843             } else
7844             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7845                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7846                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7847                 s = r = StrStr(s, ":") + 1; // beginning of path info
7848                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7849                 c = *r; *r = 0;             // temporarily null-terminate path info
7850                     *--q = 0;               // strip of trailig ':' from name
7851                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7852                 *r = c;
7853                 SendToProgram(buf,cps);     // send egtbpath command for this format
7854             }
7855             if(*p == ',') p++; // read away comma to position for next format name
7856         }
7857 }
7858
7859 void
7860 InitChessProgram(cps, setup)
7861      ChessProgramState *cps;
7862      int setup; /* [HGM] needed to setup FRC opening position */
7863 {
7864     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7865     if (appData.noChessProgram) return;
7866     hintRequested = FALSE;
7867     bookRequested = FALSE;
7868
7869     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7870     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7871     if(cps->memSize) { /* [HGM] memory */
7872         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7873         SendToProgram(buf, cps);
7874     }
7875     SendEgtPath(cps); /* [HGM] EGT */
7876     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7877         sprintf(buf, "cores %d\n", appData.smpCores);
7878         SendToProgram(buf, cps);
7879     }
7880
7881     SendToProgram(cps->initString, cps);
7882     if (gameInfo.variant != VariantNormal &&
7883         gameInfo.variant != VariantLoadable
7884         /* [HGM] also send variant if board size non-standard */
7885         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7886                                             ) {
7887       char *v = VariantName(gameInfo.variant);
7888       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7889         /* [HGM] in protocol 1 we have to assume all variants valid */
7890         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7891         DisplayFatalError(buf, 0, 1);
7892         return;
7893       }
7894
7895       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7896       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7897       if( gameInfo.variant == VariantXiangqi )
7898            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7899       if( gameInfo.variant == VariantShogi )
7900            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7901       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7902            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7903       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7904                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7905            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7906       if( gameInfo.variant == VariantCourier )
7907            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7908       if( gameInfo.variant == VariantSuper )
7909            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7910       if( gameInfo.variant == VariantGreat )
7911            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7912
7913       if(overruled) {
7914            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7915                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7916            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7917            if(StrStr(cps->variants, b) == NULL) { 
7918                // specific sized variant not known, check if general sizing allowed
7919                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7920                    if(StrStr(cps->variants, "boardsize") == NULL) {
7921                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7922                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7923                        DisplayFatalError(buf, 0, 1);
7924                        return;
7925                    }
7926                    /* [HGM] here we really should compare with the maximum supported board size */
7927                }
7928            }
7929       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7930       sprintf(buf, "variant %s\n", b);
7931       SendToProgram(buf, cps);
7932     }
7933     currentlyInitializedVariant = gameInfo.variant;
7934
7935     /* [HGM] send opening position in FRC to first engine */
7936     if(setup) {
7937           SendToProgram("force\n", cps);
7938           SendBoard(cps, 0);
7939           /* engine is now in force mode! Set flag to wake it up after first move. */
7940           setboardSpoiledMachineBlack = 1;
7941     }
7942
7943     if (cps->sendICS) {
7944       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7945       SendToProgram(buf, cps);
7946     }
7947     cps->maybeThinking = FALSE;
7948     cps->offeredDraw = 0;
7949     if (!appData.icsActive) {
7950         SendTimeControl(cps, movesPerSession, timeControl,
7951                         timeIncrement, appData.searchDepth,
7952                         searchTime);
7953     }
7954     if (appData.showThinking 
7955         // [HGM] thinking: four options require thinking output to be sent
7956         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7957                                 ) {
7958         SendToProgram("post\n", cps);
7959     }
7960     SendToProgram("hard\n", cps);
7961     if (!appData.ponderNextMove) {
7962         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7963            it without being sure what state we are in first.  "hard"
7964            is not a toggle, so that one is OK.
7965          */
7966         SendToProgram("easy\n", cps);
7967     }
7968     if (cps->usePing) {
7969       sprintf(buf, "ping %d\n", ++cps->lastPing);
7970       SendToProgram(buf, cps);
7971     }
7972     cps->initDone = TRUE;
7973 }   
7974
7975
7976 void
7977 StartChessProgram(cps)
7978      ChessProgramState *cps;
7979 {
7980     char buf[MSG_SIZ];
7981     int err;
7982
7983     if (appData.noChessProgram) return;
7984     cps->initDone = FALSE;
7985
7986     if (strcmp(cps->host, "localhost") == 0) {
7987         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7988     } else if (*appData.remoteShell == NULLCHAR) {
7989         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7990     } else {
7991         if (*appData.remoteUser == NULLCHAR) {
7992           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7993                     cps->program);
7994         } else {
7995           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7996                     cps->host, appData.remoteUser, cps->program);
7997         }
7998         err = StartChildProcess(buf, "", &cps->pr);
7999     }
8000     
8001     if (err != 0) {
8002         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8003         DisplayFatalError(buf, err, 1);
8004         cps->pr = NoProc;
8005         cps->isr = NULL;
8006         return;
8007     }
8008     
8009     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8010     if (cps->protocolVersion > 1) {
8011       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8012       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8013       cps->comboCnt = 0;  //                and values of combo boxes
8014       SendToProgram(buf, cps);
8015     } else {
8016       SendToProgram("xboard\n", cps);
8017     }
8018 }
8019
8020
8021 void
8022 TwoMachinesEventIfReady P((void))
8023 {
8024   if (first.lastPing != first.lastPong) {
8025     DisplayMessage("", _("Waiting for first chess program"));
8026     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8027     return;
8028   }
8029   if (second.lastPing != second.lastPong) {
8030     DisplayMessage("", _("Waiting for second chess program"));
8031     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8032     return;
8033   }
8034   ThawUI();
8035   TwoMachinesEvent();
8036 }
8037
8038 void
8039 NextMatchGame P((void))
8040 {
8041     int index; /* [HGM] autoinc: step load index during match */
8042     Reset(FALSE, TRUE);
8043     if (*appData.loadGameFile != NULLCHAR) {
8044         index = appData.loadGameIndex;
8045         if(index < 0) { // [HGM] autoinc
8046             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8047             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8048         } 
8049         LoadGameFromFile(appData.loadGameFile,
8050                          index,
8051                          appData.loadGameFile, FALSE);
8052     } else if (*appData.loadPositionFile != NULLCHAR) {
8053         index = appData.loadPositionIndex;
8054         if(index < 0) { // [HGM] autoinc
8055             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8056             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8057         } 
8058         LoadPositionFromFile(appData.loadPositionFile,
8059                              index,
8060                              appData.loadPositionFile);
8061     }
8062     TwoMachinesEventIfReady();
8063 }
8064
8065 void UserAdjudicationEvent( int result )
8066 {
8067     ChessMove gameResult = GameIsDrawn;
8068
8069     if( result > 0 ) {
8070         gameResult = WhiteWins;
8071     }
8072     else if( result < 0 ) {
8073         gameResult = BlackWins;
8074     }
8075
8076     if( gameMode == TwoMachinesPlay ) {
8077         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8078     }
8079 }
8080
8081
8082 // [HGM] save: calculate checksum of game to make games easily identifiable
8083 int StringCheckSum(char *s)
8084 {
8085         int i = 0;
8086         if(s==NULL) return 0;
8087         while(*s) i = i*259 + *s++;
8088         return i;
8089 }
8090
8091 int GameCheckSum()
8092 {
8093         int i, sum=0;
8094         for(i=backwardMostMove; i<forwardMostMove; i++) {
8095                 sum += pvInfoList[i].depth;
8096                 sum += StringCheckSum(parseList[i]);
8097                 sum += StringCheckSum(commentList[i]);
8098                 sum *= 261;
8099         }
8100         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8101         return sum + StringCheckSum(commentList[i]);
8102 } // end of save patch
8103
8104 void
8105 GameEnds(result, resultDetails, whosays)
8106      ChessMove result;
8107      char *resultDetails;
8108      int whosays;
8109 {
8110     GameMode nextGameMode;
8111     int isIcsGame;
8112     char buf[MSG_SIZ];
8113
8114     if(endingGame) return; /* [HGM] crash: forbid recursion */
8115     endingGame = 1;
8116
8117     if (appData.debugMode) {
8118       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8119               result, resultDetails ? resultDetails : "(null)", whosays);
8120     }
8121
8122     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8123         /* If we are playing on ICS, the server decides when the
8124            game is over, but the engine can offer to draw, claim 
8125            a draw, or resign. 
8126          */
8127 #if ZIPPY
8128         if (appData.zippyPlay && first.initDone) {
8129             if (result == GameIsDrawn) {
8130                 /* In case draw still needs to be claimed */
8131                 SendToICS(ics_prefix);
8132                 SendToICS("draw\n");
8133             } else if (StrCaseStr(resultDetails, "resign")) {
8134                 SendToICS(ics_prefix);
8135                 SendToICS("resign\n");
8136             }
8137         }
8138 #endif
8139         endingGame = 0; /* [HGM] crash */
8140         return;
8141     }
8142
8143     /* If we're loading the game from a file, stop */
8144     if (whosays == GE_FILE) {
8145       (void) StopLoadGameTimer();
8146       gameFileFP = NULL;
8147     }
8148
8149     /* Cancel draw offers */
8150     first.offeredDraw = second.offeredDraw = 0;
8151
8152     /* If this is an ICS game, only ICS can really say it's done;
8153        if not, anyone can. */
8154     isIcsGame = (gameMode == IcsPlayingWhite || 
8155                  gameMode == IcsPlayingBlack || 
8156                  gameMode == IcsObserving    || 
8157                  gameMode == IcsExamining);
8158
8159     if (!isIcsGame || whosays == GE_ICS) {
8160         /* OK -- not an ICS game, or ICS said it was done */
8161         StopClocks();
8162         if (!isIcsGame && !appData.noChessProgram) 
8163           SetUserThinkingEnables();
8164     
8165         /* [HGM] if a machine claims the game end we verify this claim */
8166         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8167             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8168                 char claimer;
8169                 ChessMove trueResult = (ChessMove) -1;
8170
8171                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8172                                             first.twoMachinesColor[0] :
8173                                             second.twoMachinesColor[0] ;
8174
8175                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8176                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8177                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8178                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8179                 } else
8180                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8181                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8182                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8183                 } else
8184                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8185                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8186                 }
8187
8188                 // now verify win claims, but not in drop games, as we don't understand those yet
8189                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8190                                                  || gameInfo.variant == VariantGreat) &&
8191                     (result == WhiteWins && claimer == 'w' ||
8192                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8193                       if (appData.debugMode) {
8194                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8195                                 result, epStatus[forwardMostMove], forwardMostMove);
8196                       }
8197                       if(result != trueResult) {
8198                               sprintf(buf, "False win claim: '%s'", resultDetails);
8199                               result = claimer == 'w' ? BlackWins : WhiteWins;
8200                               resultDetails = buf;
8201                       }
8202                 } else
8203                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8204                     && (forwardMostMove <= backwardMostMove ||
8205                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8206                         (claimer=='b')==(forwardMostMove&1))
8207                                                                                   ) {
8208                       /* [HGM] verify: draws that were not flagged are false claims */
8209                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8210                       result = claimer == 'w' ? BlackWins : WhiteWins;
8211                       resultDetails = buf;
8212                 }
8213                 /* (Claiming a loss is accepted no questions asked!) */
8214             }
8215             /* [HGM] bare: don't allow bare King to win */
8216             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8217                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8218                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8219                && result != GameIsDrawn)
8220             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8221                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8222                         int p = (int)boards[forwardMostMove][i][j] - color;
8223                         if(p >= 0 && p <= (int)WhiteKing) k++;
8224                 }
8225                 if (appData.debugMode) {
8226                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8227                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8228                 }
8229                 if(k <= 1) {
8230                         result = GameIsDrawn;
8231                         sprintf(buf, "%s but bare king", resultDetails);
8232                         resultDetails = buf;
8233                 }
8234             }
8235         }
8236
8237
8238         if(serverMoves != NULL && !loadFlag) { char c = '=';
8239             if(result==WhiteWins) c = '+';
8240             if(result==BlackWins) c = '-';
8241             if(resultDetails != NULL)
8242                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8243         }
8244         if (resultDetails != NULL) {
8245             gameInfo.result = result;
8246             gameInfo.resultDetails = StrSave(resultDetails);
8247
8248             /* display last move only if game was not loaded from file */
8249             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8250                 DisplayMove(currentMove - 1);
8251     
8252             if (forwardMostMove != 0) {
8253                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8254                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8255                                                                 ) {
8256                     if (*appData.saveGameFile != NULLCHAR) {
8257                         SaveGameToFile(appData.saveGameFile, TRUE);
8258                     } else if (appData.autoSaveGames) {
8259                         AutoSaveGame();
8260                     }
8261                     if (*appData.savePositionFile != NULLCHAR) {
8262                         SavePositionToFile(appData.savePositionFile);
8263                     }
8264                 }
8265             }
8266
8267             /* Tell program how game ended in case it is learning */
8268             /* [HGM] Moved this to after saving the PGN, just in case */
8269             /* engine died and we got here through time loss. In that */
8270             /* case we will get a fatal error writing the pipe, which */
8271             /* would otherwise lose us the PGN.                       */
8272             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8273             /* output during GameEnds should never be fatal anymore   */
8274             if (gameMode == MachinePlaysWhite ||
8275                 gameMode == MachinePlaysBlack ||
8276                 gameMode == TwoMachinesPlay ||
8277                 gameMode == IcsPlayingWhite ||
8278                 gameMode == IcsPlayingBlack ||
8279                 gameMode == BeginningOfGame) {
8280                 char buf[MSG_SIZ];
8281                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8282                         resultDetails);
8283                 if (first.pr != NoProc) {
8284                     SendToProgram(buf, &first);
8285                 }
8286                 if (second.pr != NoProc &&
8287                     gameMode == TwoMachinesPlay) {
8288                     SendToProgram(buf, &second);
8289                 }
8290             }
8291         }
8292
8293         if (appData.icsActive) {
8294             if (appData.quietPlay &&
8295                 (gameMode == IcsPlayingWhite ||
8296                  gameMode == IcsPlayingBlack)) {
8297                 SendToICS(ics_prefix);
8298                 SendToICS("set shout 1\n");
8299             }
8300             nextGameMode = IcsIdle;
8301             ics_user_moved = FALSE;
8302             /* clean up premove.  It's ugly when the game has ended and the
8303              * premove highlights are still on the board.
8304              */
8305             if (gotPremove) {
8306               gotPremove = FALSE;
8307               ClearPremoveHighlights();
8308               DrawPosition(FALSE, boards[currentMove]);
8309             }
8310             if (whosays == GE_ICS) {
8311                 switch (result) {
8312                 case WhiteWins:
8313                     if (gameMode == IcsPlayingWhite)
8314                         PlayIcsWinSound();
8315                     else if(gameMode == IcsPlayingBlack)
8316                         PlayIcsLossSound();
8317                     break;
8318                 case BlackWins:
8319                     if (gameMode == IcsPlayingBlack)
8320                         PlayIcsWinSound();
8321                     else if(gameMode == IcsPlayingWhite)
8322                         PlayIcsLossSound();
8323                     break;
8324                 case GameIsDrawn:
8325                     PlayIcsDrawSound();
8326                     break;
8327                 default:
8328                     PlayIcsUnfinishedSound();
8329                 }
8330             }
8331         } else if (gameMode == EditGame ||
8332                    gameMode == PlayFromGameFile || 
8333                    gameMode == AnalyzeMode || 
8334                    gameMode == AnalyzeFile) {
8335             nextGameMode = gameMode;
8336         } else {
8337             nextGameMode = EndOfGame;
8338         }
8339         pausing = FALSE;
8340         ModeHighlight();
8341     } else {
8342         nextGameMode = gameMode;
8343     }
8344
8345     if (appData.noChessProgram) {
8346         gameMode = nextGameMode;
8347         ModeHighlight();
8348         endingGame = 0; /* [HGM] crash */
8349         return;
8350     }
8351
8352     if (first.reuse) {
8353         /* Put first chess program into idle state */
8354         if (first.pr != NoProc &&
8355             (gameMode == MachinePlaysWhite ||
8356              gameMode == MachinePlaysBlack ||
8357              gameMode == TwoMachinesPlay ||
8358              gameMode == IcsPlayingWhite ||
8359              gameMode == IcsPlayingBlack ||
8360              gameMode == BeginningOfGame)) {
8361             SendToProgram("force\n", &first);
8362             if (first.usePing) {
8363               char buf[MSG_SIZ];
8364               sprintf(buf, "ping %d\n", ++first.lastPing);
8365               SendToProgram(buf, &first);
8366             }
8367         }
8368     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8369         /* Kill off first chess program */
8370         if (first.isr != NULL)
8371           RemoveInputSource(first.isr);
8372         first.isr = NULL;
8373     
8374         if (first.pr != NoProc) {
8375             ExitAnalyzeMode();
8376             DoSleep( appData.delayBeforeQuit );
8377             SendToProgram("quit\n", &first);
8378             DoSleep( appData.delayAfterQuit );
8379             DestroyChildProcess(first.pr, first.useSigterm);
8380         }
8381         first.pr = NoProc;
8382     }
8383     if (second.reuse) {
8384         /* Put second chess program into idle state */
8385         if (second.pr != NoProc &&
8386             gameMode == TwoMachinesPlay) {
8387             SendToProgram("force\n", &second);
8388             if (second.usePing) {
8389               char buf[MSG_SIZ];
8390               sprintf(buf, "ping %d\n", ++second.lastPing);
8391               SendToProgram(buf, &second);
8392             }
8393         }
8394     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8395         /* Kill off second chess program */
8396         if (second.isr != NULL)
8397           RemoveInputSource(second.isr);
8398         second.isr = NULL;
8399     
8400         if (second.pr != NoProc) {
8401             DoSleep( appData.delayBeforeQuit );
8402             SendToProgram("quit\n", &second);
8403             DoSleep( appData.delayAfterQuit );
8404             DestroyChildProcess(second.pr, second.useSigterm);
8405         }
8406         second.pr = NoProc;
8407     }
8408
8409     if (matchMode && gameMode == TwoMachinesPlay) {
8410         switch (result) {
8411         case WhiteWins:
8412           if (first.twoMachinesColor[0] == 'w') {
8413             first.matchWins++;
8414           } else {
8415             second.matchWins++;
8416           }
8417           break;
8418         case BlackWins:
8419           if (first.twoMachinesColor[0] == 'b') {
8420             first.matchWins++;
8421           } else {
8422             second.matchWins++;
8423           }
8424           break;
8425         default:
8426           break;
8427         }
8428         if (matchGame < appData.matchGames) {
8429             char *tmp;
8430             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8431                 tmp = first.twoMachinesColor;
8432                 first.twoMachinesColor = second.twoMachinesColor;
8433                 second.twoMachinesColor = tmp;
8434             }
8435             gameMode = nextGameMode;
8436             matchGame++;
8437             if(appData.matchPause>10000 || appData.matchPause<10)
8438                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8439             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8440             endingGame = 0; /* [HGM] crash */
8441             return;
8442         } else {
8443             char buf[MSG_SIZ];
8444             gameMode = nextGameMode;
8445             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8446                     first.tidy, second.tidy,
8447                     first.matchWins, second.matchWins,
8448                     appData.matchGames - (first.matchWins + second.matchWins));
8449             DisplayFatalError(buf, 0, 0);
8450         }
8451     }
8452     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8453         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8454       ExitAnalyzeMode();
8455     gameMode = nextGameMode;
8456     ModeHighlight();
8457     endingGame = 0;  /* [HGM] crash */
8458 }
8459
8460 /* Assumes program was just initialized (initString sent).
8461    Leaves program in force mode. */
8462 void
8463 FeedMovesToProgram(cps, upto) 
8464      ChessProgramState *cps;
8465      int upto;
8466 {
8467     int i;
8468     
8469     if (appData.debugMode)
8470       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8471               startedFromSetupPosition ? "position and " : "",
8472               backwardMostMove, upto, cps->which);
8473     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8474         // [HGM] variantswitch: make engine aware of new variant
8475         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8476                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8477         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8478         SendToProgram(buf, cps);
8479         currentlyInitializedVariant = gameInfo.variant;
8480     }
8481     SendToProgram("force\n", cps);
8482     if (startedFromSetupPosition) {
8483         SendBoard(cps, backwardMostMove);
8484     if (appData.debugMode) {
8485         fprintf(debugFP, "feedMoves\n");
8486     }
8487     }
8488     for (i = backwardMostMove; i < upto; i++) {
8489         SendMoveToProgram(i, cps);
8490     }
8491 }
8492
8493
8494 void
8495 ResurrectChessProgram()
8496 {
8497      /* The chess program may have exited.
8498         If so, restart it and feed it all the moves made so far. */
8499
8500     if (appData.noChessProgram || first.pr != NoProc) return;
8501     
8502     StartChessProgram(&first);
8503     InitChessProgram(&first, FALSE);
8504     FeedMovesToProgram(&first, currentMove);
8505
8506     if (!first.sendTime) {
8507         /* can't tell gnuchess what its clock should read,
8508            so we bow to its notion. */
8509         ResetClocks();
8510         timeRemaining[0][currentMove] = whiteTimeRemaining;
8511         timeRemaining[1][currentMove] = blackTimeRemaining;
8512     }
8513
8514     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8515                 appData.icsEngineAnalyze) && first.analysisSupport) {
8516       SendToProgram("analyze\n", &first);
8517       first.analyzing = TRUE;
8518     }
8519 }
8520
8521 /*
8522  * Button procedures
8523  */
8524 void
8525 Reset(redraw, init)
8526      int redraw, init;
8527 {
8528     int i;
8529
8530     if (appData.debugMode) {
8531         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8532                 redraw, init, gameMode);
8533     }
8534     pausing = pauseExamInvalid = FALSE;
8535     startedFromSetupPosition = blackPlaysFirst = FALSE;
8536     firstMove = TRUE;
8537     whiteFlag = blackFlag = FALSE;
8538     userOfferedDraw = FALSE;
8539     hintRequested = bookRequested = FALSE;
8540     first.maybeThinking = FALSE;
8541     second.maybeThinking = FALSE;
8542     first.bookSuspend = FALSE; // [HGM] book
8543     second.bookSuspend = FALSE;
8544     thinkOutput[0] = NULLCHAR;
8545     lastHint[0] = NULLCHAR;
8546     ClearGameInfo(&gameInfo);
8547     gameInfo.variant = StringToVariant(appData.variant);
8548     ics_user_moved = ics_clock_paused = FALSE;
8549     ics_getting_history = H_FALSE;
8550     ics_gamenum = -1;
8551     white_holding[0] = black_holding[0] = NULLCHAR;
8552     ClearProgramStats();
8553     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8554     
8555     ResetFrontEnd();
8556     ClearHighlights();
8557     flipView = appData.flipView;
8558     ClearPremoveHighlights();
8559     gotPremove = FALSE;
8560     alarmSounded = FALSE;
8561
8562     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8563     if(appData.serverMovesName != NULL) {
8564         /* [HGM] prepare to make moves file for broadcasting */
8565         clock_t t = clock();
8566         if(serverMoves != NULL) fclose(serverMoves);
8567         serverMoves = fopen(appData.serverMovesName, "r");
8568         if(serverMoves != NULL) {
8569             fclose(serverMoves);
8570             /* delay 15 sec before overwriting, so all clients can see end */
8571             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8572         }
8573         serverMoves = fopen(appData.serverMovesName, "w");
8574     }
8575
8576     ExitAnalyzeMode();
8577     gameMode = BeginningOfGame;
8578     ModeHighlight();
8579     if(appData.icsActive) gameInfo.variant = VariantNormal;
8580     currentMove = forwardMostMove = backwardMostMove = 0;
8581     InitPosition(redraw);
8582     for (i = 0; i < MAX_MOVES; i++) {
8583         if (commentList[i] != NULL) {
8584             free(commentList[i]);
8585             commentList[i] = NULL;
8586         }
8587     }
8588     ResetClocks();
8589     timeRemaining[0][0] = whiteTimeRemaining;
8590     timeRemaining[1][0] = blackTimeRemaining;
8591     if (first.pr == NULL) {
8592         StartChessProgram(&first);
8593     }
8594     if (init) {
8595             InitChessProgram(&first, startedFromSetupPosition);
8596     }
8597     DisplayTitle("");
8598     DisplayMessage("", "");
8599     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8600     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8601 }
8602
8603 void
8604 AutoPlayGameLoop()
8605 {
8606     for (;;) {
8607         if (!AutoPlayOneMove())
8608           return;
8609         if (matchMode || appData.timeDelay == 0)
8610           continue;
8611         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8612           return;
8613         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8614         break;
8615     }
8616 }
8617
8618
8619 int
8620 AutoPlayOneMove()
8621 {
8622     int fromX, fromY, toX, toY;
8623
8624     if (appData.debugMode) {
8625       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8626     }
8627
8628     if (gameMode != PlayFromGameFile)
8629       return FALSE;
8630
8631     if (currentMove >= forwardMostMove) {
8632       gameMode = EditGame;
8633       ModeHighlight();
8634
8635       /* [AS] Clear current move marker at the end of a game */
8636       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8637
8638       return FALSE;
8639     }
8640     
8641     toX = moveList[currentMove][2] - AAA;
8642     toY = moveList[currentMove][3] - ONE;
8643
8644     if (moveList[currentMove][1] == '@') {
8645         if (appData.highlightLastMove) {
8646             SetHighlights(-1, -1, toX, toY);
8647         }
8648     } else {
8649         fromX = moveList[currentMove][0] - AAA;
8650         fromY = moveList[currentMove][1] - ONE;
8651
8652         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8653
8654         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8655
8656         if (appData.highlightLastMove) {
8657             SetHighlights(fromX, fromY, toX, toY);
8658         }
8659     }
8660     DisplayMove(currentMove);
8661     SendMoveToProgram(currentMove++, &first);
8662     DisplayBothClocks();
8663     DrawPosition(FALSE, boards[currentMove]);
8664     // [HGM] PV info: always display, routine tests if empty
8665     DisplayComment(currentMove - 1, commentList[currentMove]);
8666     return TRUE;
8667 }
8668
8669
8670 int
8671 LoadGameOneMove(readAhead)
8672      ChessMove readAhead;
8673 {
8674     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8675     char promoChar = NULLCHAR;
8676     ChessMove moveType;
8677     char move[MSG_SIZ];
8678     char *p, *q;
8679     
8680     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8681         gameMode != AnalyzeMode && gameMode != Training) {
8682         gameFileFP = NULL;
8683         return FALSE;
8684     }
8685     
8686     yyboardindex = forwardMostMove;
8687     if (readAhead != (ChessMove)0) {
8688       moveType = readAhead;
8689     } else {
8690       if (gameFileFP == NULL)
8691           return FALSE;
8692       moveType = (ChessMove) yylex();
8693     }
8694     
8695     done = FALSE;
8696     switch (moveType) {
8697       case Comment:
8698         if (appData.debugMode) 
8699           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8700         p = yy_text;
8701         if (*p == '{' || *p == '[' || *p == '(') {
8702             p[strlen(p) - 1] = NULLCHAR;
8703             p++;
8704         }
8705
8706         /* append the comment but don't display it */
8707         while (*p == '\n') p++;
8708         AppendComment(currentMove, p);
8709         return TRUE;
8710
8711       case WhiteCapturesEnPassant:
8712       case BlackCapturesEnPassant:
8713       case WhitePromotionChancellor:
8714       case BlackPromotionChancellor:
8715       case WhitePromotionArchbishop:
8716       case BlackPromotionArchbishop:
8717       case WhitePromotionCentaur:
8718       case BlackPromotionCentaur:
8719       case WhitePromotionQueen:
8720       case BlackPromotionQueen:
8721       case WhitePromotionRook:
8722       case BlackPromotionRook:
8723       case WhitePromotionBishop:
8724       case BlackPromotionBishop:
8725       case WhitePromotionKnight:
8726       case BlackPromotionKnight:
8727       case WhitePromotionKing:
8728       case BlackPromotionKing:
8729       case NormalMove:
8730       case WhiteKingSideCastle:
8731       case WhiteQueenSideCastle:
8732       case BlackKingSideCastle:
8733       case BlackQueenSideCastle:
8734       case WhiteKingSideCastleWild:
8735       case WhiteQueenSideCastleWild:
8736       case BlackKingSideCastleWild:
8737       case BlackQueenSideCastleWild:
8738       /* PUSH Fabien */
8739       case WhiteHSideCastleFR:
8740       case WhiteASideCastleFR:
8741       case BlackHSideCastleFR:
8742       case BlackASideCastleFR:
8743       /* POP Fabien */
8744         if (appData.debugMode)
8745           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8746         fromX = currentMoveString[0] - AAA;
8747         fromY = currentMoveString[1] - ONE;
8748         toX = currentMoveString[2] - AAA;
8749         toY = currentMoveString[3] - ONE;
8750         promoChar = currentMoveString[4];
8751         break;
8752
8753       case WhiteDrop:
8754       case BlackDrop:
8755         if (appData.debugMode)
8756           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8757         fromX = moveType == WhiteDrop ?
8758           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8759         (int) CharToPiece(ToLower(currentMoveString[0]));
8760         fromY = DROP_RANK;
8761         toX = currentMoveString[2] - AAA;
8762         toY = currentMoveString[3] - ONE;
8763         break;
8764
8765       case WhiteWins:
8766       case BlackWins:
8767       case GameIsDrawn:
8768       case GameUnfinished:
8769         if (appData.debugMode)
8770           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8771         p = strchr(yy_text, '{');
8772         if (p == NULL) p = strchr(yy_text, '(');
8773         if (p == NULL) {
8774             p = yy_text;
8775             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8776         } else {
8777             q = strchr(p, *p == '{' ? '}' : ')');
8778             if (q != NULL) *q = NULLCHAR;
8779             p++;
8780         }
8781         GameEnds(moveType, p, GE_FILE);
8782         done = TRUE;
8783         if (cmailMsgLoaded) {
8784             ClearHighlights();
8785             flipView = WhiteOnMove(currentMove);
8786             if (moveType == GameUnfinished) flipView = !flipView;
8787             if (appData.debugMode)
8788               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8789         }
8790         break;
8791
8792       case (ChessMove) 0:       /* end of file */
8793         if (appData.debugMode)
8794           fprintf(debugFP, "Parser hit end of file\n");
8795         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8796                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8797           case MT_NONE:
8798           case MT_CHECK:
8799             break;
8800           case MT_CHECKMATE:
8801           case MT_STAINMATE:
8802             if (WhiteOnMove(currentMove)) {
8803                 GameEnds(BlackWins, "Black mates", GE_FILE);
8804             } else {
8805                 GameEnds(WhiteWins, "White mates", GE_FILE);
8806             }
8807             break;
8808           case MT_STALEMATE:
8809             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8810             break;
8811         }
8812         done = TRUE;
8813         break;
8814
8815       case MoveNumberOne:
8816         if (lastLoadGameStart == GNUChessGame) {
8817             /* GNUChessGames have numbers, but they aren't move numbers */
8818             if (appData.debugMode)
8819               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8820                       yy_text, (int) moveType);
8821             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8822         }
8823         /* else fall thru */
8824
8825       case XBoardGame:
8826       case GNUChessGame:
8827       case PGNTag:
8828         /* Reached start of next game in file */
8829         if (appData.debugMode)
8830           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8831         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8832                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8833           case MT_NONE:
8834           case MT_CHECK:
8835             break;
8836           case MT_CHECKMATE:
8837           case MT_STAINMATE:
8838             if (WhiteOnMove(currentMove)) {
8839                 GameEnds(BlackWins, "Black mates", GE_FILE);
8840             } else {
8841                 GameEnds(WhiteWins, "White mates", GE_FILE);
8842             }
8843             break;
8844           case MT_STALEMATE:
8845             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8846             break;
8847         }
8848         done = TRUE;
8849         break;
8850
8851       case PositionDiagram:     /* should not happen; ignore */
8852       case ElapsedTime:         /* ignore */
8853       case NAG:                 /* ignore */
8854         if (appData.debugMode)
8855           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8856                   yy_text, (int) moveType);
8857         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8858
8859       case IllegalMove:
8860         if (appData.testLegality) {
8861             if (appData.debugMode)
8862               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8863             sprintf(move, _("Illegal move: %d.%s%s"),
8864                     (forwardMostMove / 2) + 1,
8865                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8866             DisplayError(move, 0);
8867             done = TRUE;
8868         } else {
8869             if (appData.debugMode)
8870               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8871                       yy_text, currentMoveString);
8872             fromX = currentMoveString[0] - AAA;
8873             fromY = currentMoveString[1] - ONE;
8874             toX = currentMoveString[2] - AAA;
8875             toY = currentMoveString[3] - ONE;
8876             promoChar = currentMoveString[4];
8877         }
8878         break;
8879
8880       case AmbiguousMove:
8881         if (appData.debugMode)
8882           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8883         sprintf(move, _("Ambiguous move: %d.%s%s"),
8884                 (forwardMostMove / 2) + 1,
8885                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8886         DisplayError(move, 0);
8887         done = TRUE;
8888         break;
8889
8890       default:
8891       case ImpossibleMove:
8892         if (appData.debugMode)
8893           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8894         sprintf(move, _("Illegal move: %d.%s%s"),
8895                 (forwardMostMove / 2) + 1,
8896                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8897         DisplayError(move, 0);
8898         done = TRUE;
8899         break;
8900     }
8901
8902     if (done) {
8903         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8904             DrawPosition(FALSE, boards[currentMove]);
8905             DisplayBothClocks();
8906             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8907               DisplayComment(currentMove - 1, commentList[currentMove]);
8908         }
8909         (void) StopLoadGameTimer();
8910         gameFileFP = NULL;
8911         cmailOldMove = forwardMostMove;
8912         return FALSE;
8913     } else {
8914         /* currentMoveString is set as a side-effect of yylex */
8915         strcat(currentMoveString, "\n");
8916         strcpy(moveList[forwardMostMove], currentMoveString);
8917         
8918         thinkOutput[0] = NULLCHAR;
8919         MakeMove(fromX, fromY, toX, toY, promoChar);
8920         currentMove = forwardMostMove;
8921         return TRUE;
8922     }
8923 }
8924
8925 /* Load the nth game from the given file */
8926 int
8927 LoadGameFromFile(filename, n, title, useList)
8928      char *filename;
8929      int n;
8930      char *title;
8931      /*Boolean*/ int useList;
8932 {
8933     FILE *f;
8934     char buf[MSG_SIZ];
8935
8936     if (strcmp(filename, "-") == 0) {
8937         f = stdin;
8938         title = "stdin";
8939     } else {
8940         f = fopen(filename, "rb");
8941         if (f == NULL) {
8942           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8943             DisplayError(buf, errno);
8944             return FALSE;
8945         }
8946     }
8947     if (fseek(f, 0, 0) == -1) {
8948         /* f is not seekable; probably a pipe */
8949         useList = FALSE;
8950     }
8951     if (useList && n == 0) {
8952         int error = GameListBuild(f);
8953         if (error) {
8954             DisplayError(_("Cannot build game list"), error);
8955         } else if (!ListEmpty(&gameList) &&
8956                    ((ListGame *) gameList.tailPred)->number > 1) {
8957             GameListPopUp(f, title);
8958             return TRUE;
8959         }
8960         GameListDestroy();
8961         n = 1;
8962     }
8963     if (n == 0) n = 1;
8964     return LoadGame(f, n, title, FALSE);
8965 }
8966
8967
8968 void
8969 MakeRegisteredMove()
8970 {
8971     int fromX, fromY, toX, toY;
8972     char promoChar;
8973     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8974         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8975           case CMAIL_MOVE:
8976           case CMAIL_DRAW:
8977             if (appData.debugMode)
8978               fprintf(debugFP, "Restoring %s for game %d\n",
8979                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8980     
8981             thinkOutput[0] = NULLCHAR;
8982             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8983             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8984             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8985             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8986             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8987             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8988             MakeMove(fromX, fromY, toX, toY, promoChar);
8989             ShowMove(fromX, fromY, toX, toY);
8990               
8991             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8992                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8993               case MT_NONE:
8994               case MT_CHECK:
8995                 break;
8996                 
8997               case MT_CHECKMATE:
8998               case MT_STAINMATE:
8999                 if (WhiteOnMove(currentMove)) {
9000                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9001                 } else {
9002                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9003                 }
9004                 break;
9005                 
9006               case MT_STALEMATE:
9007                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9008                 break;
9009             }
9010
9011             break;
9012             
9013           case CMAIL_RESIGN:
9014             if (WhiteOnMove(currentMove)) {
9015                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9016             } else {
9017                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9018             }
9019             break;
9020             
9021           case CMAIL_ACCEPT:
9022             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9023             break;
9024               
9025           default:
9026             break;
9027         }
9028     }
9029
9030     return;
9031 }
9032
9033 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9034 int
9035 CmailLoadGame(f, gameNumber, title, useList)
9036      FILE *f;
9037      int gameNumber;
9038      char *title;
9039      int useList;
9040 {
9041     int retVal;
9042
9043     if (gameNumber > nCmailGames) {
9044         DisplayError(_("No more games in this message"), 0);
9045         return FALSE;
9046     }
9047     if (f == lastLoadGameFP) {
9048         int offset = gameNumber - lastLoadGameNumber;
9049         if (offset == 0) {
9050             cmailMsg[0] = NULLCHAR;
9051             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9052                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9053                 nCmailMovesRegistered--;
9054             }
9055             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9056             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9057                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9058             }
9059         } else {
9060             if (! RegisterMove()) return FALSE;
9061         }
9062     }
9063
9064     retVal = LoadGame(f, gameNumber, title, useList);
9065
9066     /* Make move registered during previous look at this game, if any */
9067     MakeRegisteredMove();
9068
9069     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9070         commentList[currentMove]
9071           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9072         DisplayComment(currentMove - 1, commentList[currentMove]);
9073     }
9074
9075     return retVal;
9076 }
9077
9078 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9079 int
9080 ReloadGame(offset)
9081      int offset;
9082 {
9083     int gameNumber = lastLoadGameNumber + offset;
9084     if (lastLoadGameFP == NULL) {
9085         DisplayError(_("No game has been loaded yet"), 0);
9086         return FALSE;
9087     }
9088     if (gameNumber <= 0) {
9089         DisplayError(_("Can't back up any further"), 0);
9090         return FALSE;
9091     }
9092     if (cmailMsgLoaded) {
9093         return CmailLoadGame(lastLoadGameFP, gameNumber,
9094                              lastLoadGameTitle, lastLoadGameUseList);
9095     } else {
9096         return LoadGame(lastLoadGameFP, gameNumber,
9097                         lastLoadGameTitle, lastLoadGameUseList);
9098     }
9099 }
9100
9101
9102
9103 /* Load the nth game from open file f */
9104 int
9105 LoadGame(f, gameNumber, title, useList)
9106      FILE *f;
9107      int gameNumber;
9108      char *title;
9109      int useList;
9110 {
9111     ChessMove cm;
9112     char buf[MSG_SIZ];
9113     int gn = gameNumber;
9114     ListGame *lg = NULL;
9115     int numPGNTags = 0;
9116     int err;
9117     GameMode oldGameMode;
9118     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9119
9120     if (appData.debugMode) 
9121         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9122
9123     if (gameMode == Training )
9124         SetTrainingModeOff();
9125
9126     oldGameMode = gameMode;
9127     if (gameMode != BeginningOfGame) {
9128       Reset(FALSE, TRUE);
9129     }
9130
9131     gameFileFP = f;
9132     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9133         fclose(lastLoadGameFP);
9134     }
9135
9136     if (useList) {
9137         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9138         
9139         if (lg) {
9140             fseek(f, lg->offset, 0);
9141             GameListHighlight(gameNumber);
9142             gn = 1;
9143         }
9144         else {
9145             DisplayError(_("Game number out of range"), 0);
9146             return FALSE;
9147         }
9148     } else {
9149         GameListDestroy();
9150         if (fseek(f, 0, 0) == -1) {
9151             if (f == lastLoadGameFP ?
9152                 gameNumber == lastLoadGameNumber + 1 :
9153                 gameNumber == 1) {
9154                 gn = 1;
9155             } else {
9156                 DisplayError(_("Can't seek on game file"), 0);
9157                 return FALSE;
9158             }
9159         }
9160     }
9161     lastLoadGameFP = f;
9162     lastLoadGameNumber = gameNumber;
9163     strcpy(lastLoadGameTitle, title);
9164     lastLoadGameUseList = useList;
9165
9166     yynewfile(f);
9167
9168     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9169       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9170                 lg->gameInfo.black);
9171             DisplayTitle(buf);
9172     } else if (*title != NULLCHAR) {
9173         if (gameNumber > 1) {
9174             sprintf(buf, "%s %d", title, gameNumber);
9175             DisplayTitle(buf);
9176         } else {
9177             DisplayTitle(title);
9178         }
9179     }
9180
9181     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9182         gameMode = PlayFromGameFile;
9183         ModeHighlight();
9184     }
9185
9186     currentMove = forwardMostMove = backwardMostMove = 0;
9187     CopyBoard(boards[0], initialPosition);
9188     StopClocks();
9189
9190     /*
9191      * Skip the first gn-1 games in the file.
9192      * Also skip over anything that precedes an identifiable 
9193      * start of game marker, to avoid being confused by 
9194      * garbage at the start of the file.  Currently 
9195      * recognized start of game markers are the move number "1",
9196      * the pattern "gnuchess .* game", the pattern
9197      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9198      * A game that starts with one of the latter two patterns
9199      * will also have a move number 1, possibly
9200      * following a position diagram.
9201      * 5-4-02: Let's try being more lenient and allowing a game to
9202      * start with an unnumbered move.  Does that break anything?
9203      */
9204     cm = lastLoadGameStart = (ChessMove) 0;
9205     while (gn > 0) {
9206         yyboardindex = forwardMostMove;
9207         cm = (ChessMove) yylex();
9208         switch (cm) {
9209           case (ChessMove) 0:
9210             if (cmailMsgLoaded) {
9211                 nCmailGames = CMAIL_MAX_GAMES - gn;
9212             } else {
9213                 Reset(TRUE, TRUE);
9214                 DisplayError(_("Game not found in file"), 0);
9215             }
9216             return FALSE;
9217
9218           case GNUChessGame:
9219           case XBoardGame:
9220             gn--;
9221             lastLoadGameStart = cm;
9222             break;
9223             
9224           case MoveNumberOne:
9225             switch (lastLoadGameStart) {
9226               case GNUChessGame:
9227               case XBoardGame:
9228               case PGNTag:
9229                 break;
9230               case MoveNumberOne:
9231               case (ChessMove) 0:
9232                 gn--;           /* count this game */
9233                 lastLoadGameStart = cm;
9234                 break;
9235               default:
9236                 /* impossible */
9237                 break;
9238             }
9239             break;
9240
9241           case PGNTag:
9242             switch (lastLoadGameStart) {
9243               case GNUChessGame:
9244               case PGNTag:
9245               case MoveNumberOne:
9246               case (ChessMove) 0:
9247                 gn--;           /* count this game */
9248                 lastLoadGameStart = cm;
9249                 break;
9250               case XBoardGame:
9251                 lastLoadGameStart = cm; /* game counted already */
9252                 break;
9253               default:
9254                 /* impossible */
9255                 break;
9256             }
9257             if (gn > 0) {
9258                 do {
9259                     yyboardindex = forwardMostMove;
9260                     cm = (ChessMove) yylex();
9261                 } while (cm == PGNTag || cm == Comment);
9262             }
9263             break;
9264
9265           case WhiteWins:
9266           case BlackWins:
9267           case GameIsDrawn:
9268             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9269                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9270                     != CMAIL_OLD_RESULT) {
9271                     nCmailResults ++ ;
9272                     cmailResult[  CMAIL_MAX_GAMES
9273                                 - gn - 1] = CMAIL_OLD_RESULT;
9274                 }
9275             }
9276             break;
9277
9278           case NormalMove:
9279             /* Only a NormalMove can be at the start of a game
9280              * without a position diagram. */
9281             if (lastLoadGameStart == (ChessMove) 0) {
9282               gn--;
9283               lastLoadGameStart = MoveNumberOne;
9284             }
9285             break;
9286
9287           default:
9288             break;
9289         }
9290     }
9291     
9292     if (appData.debugMode)
9293       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9294
9295     if (cm == XBoardGame) {
9296         /* Skip any header junk before position diagram and/or move 1 */
9297         for (;;) {
9298             yyboardindex = forwardMostMove;
9299             cm = (ChessMove) yylex();
9300
9301             if (cm == (ChessMove) 0 ||
9302                 cm == GNUChessGame || cm == XBoardGame) {
9303                 /* Empty game; pretend end-of-file and handle later */
9304                 cm = (ChessMove) 0;
9305                 break;
9306             }
9307
9308             if (cm == MoveNumberOne || cm == PositionDiagram ||
9309                 cm == PGNTag || cm == Comment)
9310               break;
9311         }
9312     } else if (cm == GNUChessGame) {
9313         if (gameInfo.event != NULL) {
9314             free(gameInfo.event);
9315         }
9316         gameInfo.event = StrSave(yy_text);
9317     }   
9318
9319     startedFromSetupPosition = FALSE;
9320     while (cm == PGNTag) {
9321         if (appData.debugMode) 
9322           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9323         err = ParsePGNTag(yy_text, &gameInfo);
9324         if (!err) numPGNTags++;
9325
9326         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9327         if(gameInfo.variant != oldVariant) {
9328             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9329             InitPosition(TRUE);
9330             oldVariant = gameInfo.variant;
9331             if (appData.debugMode) 
9332               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9333         }
9334
9335
9336         if (gameInfo.fen != NULL) {
9337           Board initial_position;
9338           startedFromSetupPosition = TRUE;
9339           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9340             Reset(TRUE, TRUE);
9341             DisplayError(_("Bad FEN position in file"), 0);
9342             return FALSE;
9343           }
9344           CopyBoard(boards[0], initial_position);
9345           if (blackPlaysFirst) {
9346             currentMove = forwardMostMove = backwardMostMove = 1;
9347             CopyBoard(boards[1], initial_position);
9348             strcpy(moveList[0], "");
9349             strcpy(parseList[0], "");
9350             timeRemaining[0][1] = whiteTimeRemaining;
9351             timeRemaining[1][1] = blackTimeRemaining;
9352             if (commentList[0] != NULL) {
9353               commentList[1] = commentList[0];
9354               commentList[0] = NULL;
9355             }
9356           } else {
9357             currentMove = forwardMostMove = backwardMostMove = 0;
9358           }
9359           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9360           {   int i;
9361               initialRulePlies = FENrulePlies;
9362               epStatus[forwardMostMove] = FENepStatus;
9363               for( i=0; i< nrCastlingRights; i++ )
9364                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9365           }
9366           yyboardindex = forwardMostMove;
9367           free(gameInfo.fen);
9368           gameInfo.fen = NULL;
9369         }
9370
9371         yyboardindex = forwardMostMove;
9372         cm = (ChessMove) yylex();
9373
9374         /* Handle comments interspersed among the tags */
9375         while (cm == Comment) {
9376             char *p;
9377             if (appData.debugMode) 
9378               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9379             p = yy_text;
9380             if (*p == '{' || *p == '[' || *p == '(') {
9381                 p[strlen(p) - 1] = NULLCHAR;
9382                 p++;
9383             }
9384             while (*p == '\n') p++;
9385             AppendComment(currentMove, p);
9386             yyboardindex = forwardMostMove;
9387             cm = (ChessMove) yylex();
9388         }
9389     }
9390
9391     /* don't rely on existence of Event tag since if game was
9392      * pasted from clipboard the Event tag may not exist
9393      */
9394     if (numPGNTags > 0){
9395         char *tags;
9396         if (gameInfo.variant == VariantNormal) {
9397           gameInfo.variant = StringToVariant(gameInfo.event);
9398         }
9399         if (!matchMode) {
9400           if( appData.autoDisplayTags ) {
9401             tags = PGNTags(&gameInfo);
9402             TagsPopUp(tags, CmailMsg());
9403             free(tags);
9404           }
9405         }
9406     } else {
9407         /* Make something up, but don't display it now */
9408         SetGameInfo();
9409         TagsPopDown();
9410     }
9411
9412     if (cm == PositionDiagram) {
9413         int i, j;
9414         char *p;
9415         Board initial_position;
9416
9417         if (appData.debugMode)
9418           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9419
9420         if (!startedFromSetupPosition) {
9421             p = yy_text;
9422             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9423               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9424                 switch (*p) {
9425                   case '[':
9426                   case '-':
9427                   case ' ':
9428                   case '\t':
9429                   case '\n':
9430                   case '\r':
9431                     break;
9432                   default:
9433                     initial_position[i][j++] = CharToPiece(*p);
9434                     break;
9435                 }
9436             while (*p == ' ' || *p == '\t' ||
9437                    *p == '\n' || *p == '\r') p++;
9438         
9439             if (strncmp(p, "black", strlen("black"))==0)
9440               blackPlaysFirst = TRUE;
9441             else
9442               blackPlaysFirst = FALSE;
9443             startedFromSetupPosition = TRUE;
9444         
9445             CopyBoard(boards[0], initial_position);
9446             if (blackPlaysFirst) {
9447                 currentMove = forwardMostMove = backwardMostMove = 1;
9448                 CopyBoard(boards[1], initial_position);
9449                 strcpy(moveList[0], "");
9450                 strcpy(parseList[0], "");
9451                 timeRemaining[0][1] = whiteTimeRemaining;
9452                 timeRemaining[1][1] = blackTimeRemaining;
9453                 if (commentList[0] != NULL) {
9454                     commentList[1] = commentList[0];
9455                     commentList[0] = NULL;
9456                 }
9457             } else {
9458                 currentMove = forwardMostMove = backwardMostMove = 0;
9459             }
9460         }
9461         yyboardindex = forwardMostMove;
9462         cm = (ChessMove) yylex();
9463     }
9464
9465     if (first.pr == NoProc) {
9466         StartChessProgram(&first);
9467     }
9468     InitChessProgram(&first, FALSE);
9469     SendToProgram("force\n", &first);
9470     if (startedFromSetupPosition) {
9471         SendBoard(&first, forwardMostMove);
9472     if (appData.debugMode) {
9473         fprintf(debugFP, "Load Game\n");
9474     }
9475         DisplayBothClocks();
9476     }      
9477
9478     /* [HGM] server: flag to write setup moves in broadcast file as one */
9479     loadFlag = appData.suppressLoadMoves;
9480
9481     while (cm == Comment) {
9482         char *p;
9483         if (appData.debugMode) 
9484           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9485         p = yy_text;
9486         if (*p == '{' || *p == '[' || *p == '(') {
9487             p[strlen(p) - 1] = NULLCHAR;
9488             p++;
9489         }
9490         while (*p == '\n') p++;
9491         AppendComment(currentMove, p);
9492         yyboardindex = forwardMostMove;
9493         cm = (ChessMove) yylex();
9494     }
9495
9496     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9497         cm == WhiteWins || cm == BlackWins ||
9498         cm == GameIsDrawn || cm == GameUnfinished) {
9499         DisplayMessage("", _("No moves in game"));
9500         if (cmailMsgLoaded) {
9501             if (appData.debugMode)
9502               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9503             ClearHighlights();
9504             flipView = FALSE;
9505         }
9506         DrawPosition(FALSE, boards[currentMove]);
9507         DisplayBothClocks();
9508         gameMode = EditGame;
9509         ModeHighlight();
9510         gameFileFP = NULL;
9511         cmailOldMove = 0;
9512         return TRUE;
9513     }
9514
9515     // [HGM] PV info: routine tests if comment empty
9516     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9517         DisplayComment(currentMove - 1, commentList[currentMove]);
9518     }
9519     if (!matchMode && appData.timeDelay != 0) 
9520       DrawPosition(FALSE, boards[currentMove]);
9521
9522     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9523       programStats.ok_to_send = 1;
9524     }
9525
9526     /* if the first token after the PGN tags is a move
9527      * and not move number 1, retrieve it from the parser 
9528      */
9529     if (cm != MoveNumberOne)
9530         LoadGameOneMove(cm);
9531
9532     /* load the remaining moves from the file */
9533     while (LoadGameOneMove((ChessMove)0)) {
9534       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9535       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9536     }
9537
9538     /* rewind to the start of the game */
9539     currentMove = backwardMostMove;
9540
9541     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9542
9543     if (oldGameMode == AnalyzeFile ||
9544         oldGameMode == AnalyzeMode) {
9545       AnalyzeFileEvent();
9546     }
9547
9548     if (matchMode || appData.timeDelay == 0) {
9549       ToEndEvent();
9550       gameMode = EditGame;
9551       ModeHighlight();
9552     } else if (appData.timeDelay > 0) {
9553       AutoPlayGameLoop();
9554     }
9555
9556     if (appData.debugMode) 
9557         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9558
9559     loadFlag = 0; /* [HGM] true game starts */
9560     return TRUE;
9561 }
9562
9563 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9564 int
9565 ReloadPosition(offset)
9566      int offset;
9567 {
9568     int positionNumber = lastLoadPositionNumber + offset;
9569     if (lastLoadPositionFP == NULL) {
9570         DisplayError(_("No position has been loaded yet"), 0);
9571         return FALSE;
9572     }
9573     if (positionNumber <= 0) {
9574         DisplayError(_("Can't back up any further"), 0);
9575         return FALSE;
9576     }
9577     return LoadPosition(lastLoadPositionFP, positionNumber,
9578                         lastLoadPositionTitle);
9579 }
9580
9581 /* Load the nth position from the given file */
9582 int
9583 LoadPositionFromFile(filename, n, title)
9584      char *filename;
9585      int n;
9586      char *title;
9587 {
9588     FILE *f;
9589     char buf[MSG_SIZ];
9590
9591     if (strcmp(filename, "-") == 0) {
9592         return LoadPosition(stdin, n, "stdin");
9593     } else {
9594         f = fopen(filename, "rb");
9595         if (f == NULL) {
9596             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9597             DisplayError(buf, errno);
9598             return FALSE;
9599         } else {
9600             return LoadPosition(f, n, title);
9601         }
9602     }
9603 }
9604
9605 /* Load the nth position from the given open file, and close it */
9606 int
9607 LoadPosition(f, positionNumber, title)
9608      FILE *f;
9609      int positionNumber;
9610      char *title;
9611 {
9612     char *p, line[MSG_SIZ];
9613     Board initial_position;
9614     int i, j, fenMode, pn;
9615     
9616     if (gameMode == Training )
9617         SetTrainingModeOff();
9618
9619     if (gameMode != BeginningOfGame) {
9620         Reset(FALSE, TRUE);
9621     }
9622     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9623         fclose(lastLoadPositionFP);
9624     }
9625     if (positionNumber == 0) positionNumber = 1;
9626     lastLoadPositionFP = f;
9627     lastLoadPositionNumber = positionNumber;
9628     strcpy(lastLoadPositionTitle, title);
9629     if (first.pr == NoProc) {
9630       StartChessProgram(&first);
9631       InitChessProgram(&first, FALSE);
9632     }    
9633     pn = positionNumber;
9634     if (positionNumber < 0) {
9635         /* Negative position number means to seek to that byte offset */
9636         if (fseek(f, -positionNumber, 0) == -1) {
9637             DisplayError(_("Can't seek on position file"), 0);
9638             return FALSE;
9639         };
9640         pn = 1;
9641     } else {
9642         if (fseek(f, 0, 0) == -1) {
9643             if (f == lastLoadPositionFP ?
9644                 positionNumber == lastLoadPositionNumber + 1 :
9645                 positionNumber == 1) {
9646                 pn = 1;
9647             } else {
9648                 DisplayError(_("Can't seek on position file"), 0);
9649                 return FALSE;
9650             }
9651         }
9652     }
9653     /* See if this file is FEN or old-style xboard */
9654     if (fgets(line, MSG_SIZ, f) == NULL) {
9655         DisplayError(_("Position not found in file"), 0);
9656         return FALSE;
9657     }
9658     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9659     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9660
9661     if (pn >= 2) {
9662         if (fenMode || line[0] == '#') pn--;
9663         while (pn > 0) {
9664             /* skip positions before number pn */
9665             if (fgets(line, MSG_SIZ, f) == NULL) {
9666                 Reset(TRUE, TRUE);
9667                 DisplayError(_("Position not found in file"), 0);
9668                 return FALSE;
9669             }
9670             if (fenMode || line[0] == '#') pn--;
9671         }
9672     }
9673
9674     if (fenMode) {
9675         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9676             DisplayError(_("Bad FEN position in file"), 0);
9677             return FALSE;
9678         }
9679     } else {
9680         (void) fgets(line, MSG_SIZ, f);
9681         (void) fgets(line, MSG_SIZ, f);
9682     
9683         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9684             (void) fgets(line, MSG_SIZ, f);
9685             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9686                 if (*p == ' ')
9687                   continue;
9688                 initial_position[i][j++] = CharToPiece(*p);
9689             }
9690         }
9691     
9692         blackPlaysFirst = FALSE;
9693         if (!feof(f)) {
9694             (void) fgets(line, MSG_SIZ, f);
9695             if (strncmp(line, "black", strlen("black"))==0)
9696               blackPlaysFirst = TRUE;
9697         }
9698     }
9699     startedFromSetupPosition = TRUE;
9700     
9701     SendToProgram("force\n", &first);
9702     CopyBoard(boards[0], initial_position);
9703     if (blackPlaysFirst) {
9704         currentMove = forwardMostMove = backwardMostMove = 1;
9705         strcpy(moveList[0], "");
9706         strcpy(parseList[0], "");
9707         CopyBoard(boards[1], initial_position);
9708         DisplayMessage("", _("Black to play"));
9709     } else {
9710         currentMove = forwardMostMove = backwardMostMove = 0;
9711         DisplayMessage("", _("White to play"));
9712     }
9713           /* [HGM] copy FEN attributes as well */
9714           {   int i;
9715               initialRulePlies = FENrulePlies;
9716               epStatus[forwardMostMove] = FENepStatus;
9717               for( i=0; i< nrCastlingRights; i++ )
9718                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9719           }
9720     SendBoard(&first, forwardMostMove);
9721     if (appData.debugMode) {
9722 int i, j;
9723   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9724   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9725         fprintf(debugFP, "Load Position\n");
9726     }
9727
9728     if (positionNumber > 1) {
9729         sprintf(line, "%s %d", title, positionNumber);
9730         DisplayTitle(line);
9731     } else {
9732         DisplayTitle(title);
9733     }
9734     gameMode = EditGame;
9735     ModeHighlight();
9736     ResetClocks();
9737     timeRemaining[0][1] = whiteTimeRemaining;
9738     timeRemaining[1][1] = blackTimeRemaining;
9739     DrawPosition(FALSE, boards[currentMove]);
9740    
9741     return TRUE;
9742 }
9743
9744
9745 void
9746 CopyPlayerNameIntoFileName(dest, src)
9747      char **dest, *src;
9748 {
9749     while (*src != NULLCHAR && *src != ',') {
9750         if (*src == ' ') {
9751             *(*dest)++ = '_';
9752             src++;
9753         } else {
9754             *(*dest)++ = *src++;
9755         }
9756     }
9757 }
9758
9759 char *DefaultFileName(ext)
9760      char *ext;
9761 {
9762     static char def[MSG_SIZ];
9763     char *p;
9764
9765     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9766         p = def;
9767         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9768         *p++ = '-';
9769         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9770         *p++ = '.';
9771         strcpy(p, ext);
9772     } else {
9773         def[0] = NULLCHAR;
9774     }
9775     return def;
9776 }
9777
9778 /* Save the current game to the given file */
9779 int
9780 SaveGameToFile(filename, append)
9781      char *filename;
9782      int append;
9783 {
9784     FILE *f;
9785     char buf[MSG_SIZ];
9786
9787     if (strcmp(filename, "-") == 0) {
9788         return SaveGame(stdout, 0, NULL);
9789     } else {
9790         f = fopen(filename, append ? "a" : "w");
9791         if (f == NULL) {
9792             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9793             DisplayError(buf, errno);
9794             return FALSE;
9795         } else {
9796             return SaveGame(f, 0, NULL);
9797         }
9798     }
9799 }
9800
9801 char *
9802 SavePart(str)
9803      char *str;
9804 {
9805     static char buf[MSG_SIZ];
9806     char *p;
9807     
9808     p = strchr(str, ' ');
9809     if (p == NULL) return str;
9810     strncpy(buf, str, p - str);
9811     buf[p - str] = NULLCHAR;
9812     return buf;
9813 }
9814
9815 #define PGN_MAX_LINE 75
9816
9817 #define PGN_SIDE_WHITE  0
9818 #define PGN_SIDE_BLACK  1
9819
9820 /* [AS] */
9821 static int FindFirstMoveOutOfBook( int side )
9822 {
9823     int result = -1;
9824
9825     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9826         int index = backwardMostMove;
9827         int has_book_hit = 0;
9828
9829         if( (index % 2) != side ) {
9830             index++;
9831         }
9832
9833         while( index < forwardMostMove ) {
9834             /* Check to see if engine is in book */
9835             int depth = pvInfoList[index].depth;
9836             int score = pvInfoList[index].score;
9837             int in_book = 0;
9838
9839             if( depth <= 2 ) {
9840                 in_book = 1;
9841             }
9842             else if( score == 0 && depth == 63 ) {
9843                 in_book = 1; /* Zappa */
9844             }
9845             else if( score == 2 && depth == 99 ) {
9846                 in_book = 1; /* Abrok */
9847             }
9848
9849             has_book_hit += in_book;
9850
9851             if( ! in_book ) {
9852                 result = index;
9853
9854                 break;
9855             }
9856
9857             index += 2;
9858         }
9859     }
9860
9861     return result;
9862 }
9863
9864 /* [AS] */
9865 void GetOutOfBookInfo( char * buf )
9866 {
9867     int oob[2];
9868     int i;
9869     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9870
9871     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9872     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9873
9874     *buf = '\0';
9875
9876     if( oob[0] >= 0 || oob[1] >= 0 ) {
9877         for( i=0; i<2; i++ ) {
9878             int idx = oob[i];
9879
9880             if( idx >= 0 ) {
9881                 if( i > 0 && oob[0] >= 0 ) {
9882                     strcat( buf, "   " );
9883                 }
9884
9885                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9886                 sprintf( buf+strlen(buf), "%s%.2f", 
9887                     pvInfoList[idx].score >= 0 ? "+" : "",
9888                     pvInfoList[idx].score / 100.0 );
9889             }
9890         }
9891     }
9892 }
9893
9894 /* Save game in PGN style and close the file */
9895 int
9896 SaveGamePGN(f)
9897      FILE *f;
9898 {
9899     int i, offset, linelen, newblock;
9900     time_t tm;
9901 //    char *movetext;
9902     char numtext[32];
9903     int movelen, numlen, blank;
9904     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9905
9906     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9907     
9908     tm = time((time_t *) NULL);
9909     
9910     PrintPGNTags(f, &gameInfo);
9911     
9912     if (backwardMostMove > 0 || startedFromSetupPosition) {
9913         char *fen = PositionToFEN(backwardMostMove, NULL);
9914         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9915         fprintf(f, "\n{--------------\n");
9916         PrintPosition(f, backwardMostMove);
9917         fprintf(f, "--------------}\n");
9918         free(fen);
9919     }
9920     else {
9921         /* [AS] Out of book annotation */
9922         if( appData.saveOutOfBookInfo ) {
9923             char buf[64];
9924
9925             GetOutOfBookInfo( buf );
9926
9927             if( buf[0] != '\0' ) {
9928                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9929             }
9930         }
9931
9932         fprintf(f, "\n");
9933     }
9934
9935     i = backwardMostMove;
9936     linelen = 0;
9937     newblock = TRUE;
9938
9939     while (i < forwardMostMove) {
9940         /* Print comments preceding this move */
9941         if (commentList[i] != NULL) {
9942             if (linelen > 0) fprintf(f, "\n");
9943             fprintf(f, "{\n%s}\n", commentList[i]);
9944             linelen = 0;
9945             newblock = TRUE;
9946         }
9947
9948         /* Format move number */
9949         if ((i % 2) == 0) {
9950             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9951         } else {
9952             if (newblock) {
9953                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9954             } else {
9955                 numtext[0] = NULLCHAR;
9956             }
9957         }
9958         numlen = strlen(numtext);
9959         newblock = FALSE;
9960
9961         /* Print move number */
9962         blank = linelen > 0 && numlen > 0;
9963         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9964             fprintf(f, "\n");
9965             linelen = 0;
9966             blank = 0;
9967         }
9968         if (blank) {
9969             fprintf(f, " ");
9970             linelen++;
9971         }
9972         fprintf(f, "%s", numtext);
9973         linelen += numlen;
9974
9975         /* Get move */
9976         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9977         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9978
9979         /* Print move */
9980         blank = linelen > 0 && movelen > 0;
9981         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9982             fprintf(f, "\n");
9983             linelen = 0;
9984             blank = 0;
9985         }
9986         if (blank) {
9987             fprintf(f, " ");
9988             linelen++;
9989         }
9990         fprintf(f, "%s", move_buffer);
9991         linelen += movelen;
9992
9993         /* [AS] Add PV info if present */
9994         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9995             /* [HGM] add time */
9996             char buf[MSG_SIZ]; int seconds;
9997
9998             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9999
10000             if( seconds <= 0) buf[0] = 0; else
10001             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10002                 seconds = (seconds + 4)/10; // round to full seconds
10003                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10004                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10005             }
10006
10007             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10008                 pvInfoList[i].score >= 0 ? "+" : "",
10009                 pvInfoList[i].score / 100.0,
10010                 pvInfoList[i].depth,
10011                 buf );
10012
10013             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10014
10015             /* Print score/depth */
10016             blank = linelen > 0 && movelen > 0;
10017             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10018                 fprintf(f, "\n");
10019                 linelen = 0;
10020                 blank = 0;
10021             }
10022             if (blank) {
10023                 fprintf(f, " ");
10024                 linelen++;
10025             }
10026             fprintf(f, "%s", move_buffer);
10027             linelen += movelen;
10028         }
10029
10030         i++;
10031     }
10032     
10033     /* Start a new line */
10034     if (linelen > 0) fprintf(f, "\n");
10035
10036     /* Print comments after last move */
10037     if (commentList[i] != NULL) {
10038         fprintf(f, "{\n%s}\n", commentList[i]);
10039     }
10040
10041     /* Print result */
10042     if (gameInfo.resultDetails != NULL &&
10043         gameInfo.resultDetails[0] != NULLCHAR) {
10044         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10045                 PGNResult(gameInfo.result));
10046     } else {
10047         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10048     }
10049
10050     fclose(f);
10051     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10052     return TRUE;
10053 }
10054
10055 /* Save game in old style and close the file */
10056 int
10057 SaveGameOldStyle(f)
10058      FILE *f;
10059 {
10060     int i, offset;
10061     time_t tm;
10062     
10063     tm = time((time_t *) NULL);
10064     
10065     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10066     PrintOpponents(f);
10067     
10068     if (backwardMostMove > 0 || startedFromSetupPosition) {
10069         fprintf(f, "\n[--------------\n");
10070         PrintPosition(f, backwardMostMove);
10071         fprintf(f, "--------------]\n");
10072     } else {
10073         fprintf(f, "\n");
10074     }
10075
10076     i = backwardMostMove;
10077     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10078
10079     while (i < forwardMostMove) {
10080         if (commentList[i] != NULL) {
10081             fprintf(f, "[%s]\n", commentList[i]);
10082         }
10083
10084         if ((i % 2) == 1) {
10085             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10086             i++;
10087         } else {
10088             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10089             i++;
10090             if (commentList[i] != NULL) {
10091                 fprintf(f, "\n");
10092                 continue;
10093             }
10094             if (i >= forwardMostMove) {
10095                 fprintf(f, "\n");
10096                 break;
10097             }
10098             fprintf(f, "%s\n", parseList[i]);
10099             i++;
10100         }
10101     }
10102     
10103     if (commentList[i] != NULL) {
10104         fprintf(f, "[%s]\n", commentList[i]);
10105     }
10106
10107     /* This isn't really the old style, but it's close enough */
10108     if (gameInfo.resultDetails != NULL &&
10109         gameInfo.resultDetails[0] != NULLCHAR) {
10110         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10111                 gameInfo.resultDetails);
10112     } else {
10113         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10114     }
10115
10116     fclose(f);
10117     return TRUE;
10118 }
10119
10120 /* Save the current game to open file f and close the file */
10121 int
10122 SaveGame(f, dummy, dummy2)
10123      FILE *f;
10124      int dummy;
10125      char *dummy2;
10126 {
10127     if (gameMode == EditPosition) EditPositionDone(TRUE);
10128     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10129     if (appData.oldSaveStyle)
10130       return SaveGameOldStyle(f);
10131     else
10132       return SaveGamePGN(f);
10133 }
10134
10135 /* Save the current position to the given file */
10136 int
10137 SavePositionToFile(filename)
10138      char *filename;
10139 {
10140     FILE *f;
10141     char buf[MSG_SIZ];
10142
10143     if (strcmp(filename, "-") == 0) {
10144         return SavePosition(stdout, 0, NULL);
10145     } else {
10146         f = fopen(filename, "a");
10147         if (f == NULL) {
10148             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10149             DisplayError(buf, errno);
10150             return FALSE;
10151         } else {
10152             SavePosition(f, 0, NULL);
10153             return TRUE;
10154         }
10155     }
10156 }
10157
10158 /* Save the current position to the given open file and close the file */
10159 int
10160 SavePosition(f, dummy, dummy2)
10161      FILE *f;
10162      int dummy;
10163      char *dummy2;
10164 {
10165     time_t tm;
10166     char *fen;
10167
10168     if (gameMode == EditPosition) EditPositionDone(TRUE);
10169     if (appData.oldSaveStyle) {
10170         tm = time((time_t *) NULL);
10171     
10172         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10173         PrintOpponents(f);
10174         fprintf(f, "[--------------\n");
10175         PrintPosition(f, currentMove);
10176         fprintf(f, "--------------]\n");
10177     } else {
10178         fen = PositionToFEN(currentMove, NULL);
10179         fprintf(f, "%s\n", fen);
10180         free(fen);
10181     }
10182     fclose(f);
10183     return TRUE;
10184 }
10185
10186 void
10187 ReloadCmailMsgEvent(unregister)
10188      int unregister;
10189 {
10190 #if !WIN32
10191     static char *inFilename = NULL;
10192     static char *outFilename;
10193     int i;
10194     struct stat inbuf, outbuf;
10195     int status;
10196     
10197     /* Any registered moves are unregistered if unregister is set, */
10198     /* i.e. invoked by the signal handler */
10199     if (unregister) {
10200         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10201             cmailMoveRegistered[i] = FALSE;
10202             if (cmailCommentList[i] != NULL) {
10203                 free(cmailCommentList[i]);
10204                 cmailCommentList[i] = NULL;
10205             }
10206         }
10207         nCmailMovesRegistered = 0;
10208     }
10209
10210     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10211         cmailResult[i] = CMAIL_NOT_RESULT;
10212     }
10213     nCmailResults = 0;
10214
10215     if (inFilename == NULL) {
10216         /* Because the filenames are static they only get malloced once  */
10217         /* and they never get freed                                      */
10218         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10219         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10220
10221         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10222         sprintf(outFilename, "%s.out", appData.cmailGameName);
10223     }
10224     
10225     status = stat(outFilename, &outbuf);
10226     if (status < 0) {
10227         cmailMailedMove = FALSE;
10228     } else {
10229         status = stat(inFilename, &inbuf);
10230         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10231     }
10232     
10233     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10234        counts the games, notes how each one terminated, etc.
10235        
10236        It would be nice to remove this kludge and instead gather all
10237        the information while building the game list.  (And to keep it
10238        in the game list nodes instead of having a bunch of fixed-size
10239        parallel arrays.)  Note this will require getting each game's
10240        termination from the PGN tags, as the game list builder does
10241        not process the game moves.  --mann
10242        */
10243     cmailMsgLoaded = TRUE;
10244     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10245     
10246     /* Load first game in the file or popup game menu */
10247     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10248
10249 #endif /* !WIN32 */
10250     return;
10251 }
10252
10253 int
10254 RegisterMove()
10255 {
10256     FILE *f;
10257     char string[MSG_SIZ];
10258
10259     if (   cmailMailedMove
10260         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10261         return TRUE;            /* Allow free viewing  */
10262     }
10263
10264     /* Unregister move to ensure that we don't leave RegisterMove        */
10265     /* with the move registered when the conditions for registering no   */
10266     /* longer hold                                                       */
10267     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10268         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10269         nCmailMovesRegistered --;
10270
10271         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10272           {
10273               free(cmailCommentList[lastLoadGameNumber - 1]);
10274               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10275           }
10276     }
10277
10278     if (cmailOldMove == -1) {
10279         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10280         return FALSE;
10281     }
10282
10283     if (currentMove > cmailOldMove + 1) {
10284         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10285         return FALSE;
10286     }
10287
10288     if (currentMove < cmailOldMove) {
10289         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10290         return FALSE;
10291     }
10292
10293     if (forwardMostMove > currentMove) {
10294         /* Silently truncate extra moves */
10295         TruncateGame();
10296     }
10297
10298     if (   (currentMove == cmailOldMove + 1)
10299         || (   (currentMove == cmailOldMove)
10300             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10301                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10302         if (gameInfo.result != GameUnfinished) {
10303             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10304         }
10305
10306         if (commentList[currentMove] != NULL) {
10307             cmailCommentList[lastLoadGameNumber - 1]
10308               = StrSave(commentList[currentMove]);
10309         }
10310         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10311
10312         if (appData.debugMode)
10313           fprintf(debugFP, "Saving %s for game %d\n",
10314                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10315
10316         sprintf(string,
10317                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10318         
10319         f = fopen(string, "w");
10320         if (appData.oldSaveStyle) {
10321             SaveGameOldStyle(f); /* also closes the file */
10322             
10323             sprintf(string, "%s.pos.out", appData.cmailGameName);
10324             f = fopen(string, "w");
10325             SavePosition(f, 0, NULL); /* also closes the file */
10326         } else {
10327             fprintf(f, "{--------------\n");
10328             PrintPosition(f, currentMove);
10329             fprintf(f, "--------------}\n\n");
10330             
10331             SaveGame(f, 0, NULL); /* also closes the file*/
10332         }
10333         
10334         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10335         nCmailMovesRegistered ++;
10336     } else if (nCmailGames == 1) {
10337         DisplayError(_("You have not made a move yet"), 0);
10338         return FALSE;
10339     }
10340
10341     return TRUE;
10342 }
10343
10344 void
10345 MailMoveEvent()
10346 {
10347 #if !WIN32
10348     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10349     FILE *commandOutput;
10350     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10351     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10352     int nBuffers;
10353     int i;
10354     int archived;
10355     char *arcDir;
10356
10357     if (! cmailMsgLoaded) {
10358         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10359         return;
10360     }
10361
10362     if (nCmailGames == nCmailResults) {
10363         DisplayError(_("No unfinished games"), 0);
10364         return;
10365     }
10366
10367 #if CMAIL_PROHIBIT_REMAIL
10368     if (cmailMailedMove) {
10369         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);
10370         DisplayError(msg, 0);
10371         return;
10372     }
10373 #endif
10374
10375     if (! (cmailMailedMove || RegisterMove())) return;
10376     
10377     if (   cmailMailedMove
10378         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10379         sprintf(string, partCommandString,
10380                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10381         commandOutput = popen(string, "r");
10382
10383         if (commandOutput == NULL) {
10384             DisplayError(_("Failed to invoke cmail"), 0);
10385         } else {
10386             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10387                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10388             }
10389             if (nBuffers > 1) {
10390                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10391                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10392                 nBytes = MSG_SIZ - 1;
10393             } else {
10394                 (void) memcpy(msg, buffer, nBytes);
10395             }
10396             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10397
10398             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10399                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10400
10401                 archived = TRUE;
10402                 for (i = 0; i < nCmailGames; i ++) {
10403                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10404                         archived = FALSE;
10405                     }
10406                 }
10407                 if (   archived
10408                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10409                         != NULL)) {
10410                     sprintf(buffer, "%s/%s.%s.archive",
10411                             arcDir,
10412                             appData.cmailGameName,
10413                             gameInfo.date);
10414                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10415                     cmailMsgLoaded = FALSE;
10416                 }
10417             }
10418
10419             DisplayInformation(msg);
10420             pclose(commandOutput);
10421         }
10422     } else {
10423         if ((*cmailMsg) != '\0') {
10424             DisplayInformation(cmailMsg);
10425         }
10426     }
10427
10428     return;
10429 #endif /* !WIN32 */
10430 }
10431
10432 char *
10433 CmailMsg()
10434 {
10435 #if WIN32
10436     return NULL;
10437 #else
10438     int  prependComma = 0;
10439     char number[5];
10440     char string[MSG_SIZ];       /* Space for game-list */
10441     int  i;
10442     
10443     if (!cmailMsgLoaded) return "";
10444
10445     if (cmailMailedMove) {
10446         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10447     } else {
10448         /* Create a list of games left */
10449         sprintf(string, "[");
10450         for (i = 0; i < nCmailGames; i ++) {
10451             if (! (   cmailMoveRegistered[i]
10452                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10453                 if (prependComma) {
10454                     sprintf(number, ",%d", i + 1);
10455                 } else {
10456                     sprintf(number, "%d", i + 1);
10457                     prependComma = 1;
10458                 }
10459                 
10460                 strcat(string, number);
10461             }
10462         }
10463         strcat(string, "]");
10464
10465         if (nCmailMovesRegistered + nCmailResults == 0) {
10466             switch (nCmailGames) {
10467               case 1:
10468                 sprintf(cmailMsg,
10469                         _("Still need to make move for game\n"));
10470                 break;
10471                 
10472               case 2:
10473                 sprintf(cmailMsg,
10474                         _("Still need to make moves for both games\n"));
10475                 break;
10476                 
10477               default:
10478                 sprintf(cmailMsg,
10479                         _("Still need to make moves for all %d games\n"),
10480                         nCmailGames);
10481                 break;
10482             }
10483         } else {
10484             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10485               case 1:
10486                 sprintf(cmailMsg,
10487                         _("Still need to make a move for game %s\n"),
10488                         string);
10489                 break;
10490                 
10491               case 0:
10492                 if (nCmailResults == nCmailGames) {
10493                     sprintf(cmailMsg, _("No unfinished games\n"));
10494                 } else {
10495                     sprintf(cmailMsg, _("Ready to send mail\n"));
10496                 }
10497                 break;
10498                 
10499               default:
10500                 sprintf(cmailMsg,
10501                         _("Still need to make moves for games %s\n"),
10502                         string);
10503             }
10504         }
10505     }
10506     return cmailMsg;
10507 #endif /* WIN32 */
10508 }
10509
10510 void
10511 ResetGameEvent()
10512 {
10513     if (gameMode == Training)
10514       SetTrainingModeOff();
10515
10516     Reset(TRUE, TRUE);
10517     cmailMsgLoaded = FALSE;
10518     if (appData.icsActive) {
10519       SendToICS(ics_prefix);
10520       SendToICS("refresh\n");
10521     }
10522 }
10523
10524 void
10525 ExitEvent(status)
10526      int status;
10527 {
10528     exiting++;
10529     if (exiting > 2) {
10530       /* Give up on clean exit */
10531       exit(status);
10532     }
10533     if (exiting > 1) {
10534       /* Keep trying for clean exit */
10535       return;
10536     }
10537
10538     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10539
10540     if (telnetISR != NULL) {
10541       RemoveInputSource(telnetISR);
10542     }
10543     if (icsPR != NoProc) {
10544       DestroyChildProcess(icsPR, TRUE);
10545     }
10546
10547     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10548     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10549
10550     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10551     /* make sure this other one finishes before killing it!                  */
10552     if(endingGame) { int count = 0;
10553         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10554         while(endingGame && count++ < 10) DoSleep(1);
10555         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10556     }
10557
10558     /* Kill off chess programs */
10559     if (first.pr != NoProc) {
10560         ExitAnalyzeMode();
10561         
10562         DoSleep( appData.delayBeforeQuit );
10563         SendToProgram("quit\n", &first);
10564         DoSleep( appData.delayAfterQuit );
10565         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10566     }
10567     if (second.pr != NoProc) {
10568         DoSleep( appData.delayBeforeQuit );
10569         SendToProgram("quit\n", &second);
10570         DoSleep( appData.delayAfterQuit );
10571         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10572     }
10573     if (first.isr != NULL) {
10574         RemoveInputSource(first.isr);
10575     }
10576     if (second.isr != NULL) {
10577         RemoveInputSource(second.isr);
10578     }
10579
10580     ShutDownFrontEnd();
10581     exit(status);
10582 }
10583
10584 void
10585 PauseEvent()
10586 {
10587     if (appData.debugMode)
10588         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10589     if (pausing) {
10590         pausing = FALSE;
10591         ModeHighlight();
10592         if (gameMode == MachinePlaysWhite ||
10593             gameMode == MachinePlaysBlack) {
10594             StartClocks();
10595         } else {
10596             DisplayBothClocks();
10597         }
10598         if (gameMode == PlayFromGameFile) {
10599             if (appData.timeDelay >= 0) 
10600                 AutoPlayGameLoop();
10601         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10602             Reset(FALSE, TRUE);
10603             SendToICS(ics_prefix);
10604             SendToICS("refresh\n");
10605         } else if (currentMove < forwardMostMove) {
10606             ForwardInner(forwardMostMove);
10607         }
10608         pauseExamInvalid = FALSE;
10609     } else {
10610         switch (gameMode) {
10611           default:
10612             return;
10613           case IcsExamining:
10614             pauseExamForwardMostMove = forwardMostMove;
10615             pauseExamInvalid = FALSE;
10616             /* fall through */
10617           case IcsObserving:
10618           case IcsPlayingWhite:
10619           case IcsPlayingBlack:
10620             pausing = TRUE;
10621             ModeHighlight();
10622             return;
10623           case PlayFromGameFile:
10624             (void) StopLoadGameTimer();
10625             pausing = TRUE;
10626             ModeHighlight();
10627             break;
10628           case BeginningOfGame:
10629             if (appData.icsActive) return;
10630             /* else fall through */
10631           case MachinePlaysWhite:
10632           case MachinePlaysBlack:
10633           case TwoMachinesPlay:
10634             if (forwardMostMove == 0)
10635               return;           /* don't pause if no one has moved */
10636             if ((gameMode == MachinePlaysWhite &&
10637                  !WhiteOnMove(forwardMostMove)) ||
10638                 (gameMode == MachinePlaysBlack &&
10639                  WhiteOnMove(forwardMostMove))) {
10640                 StopClocks();
10641             }
10642             pausing = TRUE;
10643             ModeHighlight();
10644             break;
10645         }
10646     }
10647 }
10648
10649 void
10650 EditCommentEvent()
10651 {
10652     char title[MSG_SIZ];
10653
10654     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10655         strcpy(title, _("Edit comment"));
10656     } else {
10657         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10658                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10659                 parseList[currentMove - 1]);
10660     }
10661
10662     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10663 }
10664
10665
10666 void
10667 EditTagsEvent()
10668 {
10669     char *tags = PGNTags(&gameInfo);
10670     EditTagsPopUp(tags);
10671     free(tags);
10672 }
10673
10674 void
10675 AnalyzeModeEvent()
10676 {
10677     if (appData.noChessProgram || gameMode == AnalyzeMode)
10678       return;
10679
10680     if (gameMode != AnalyzeFile) {
10681         if (!appData.icsEngineAnalyze) {
10682                EditGameEvent();
10683                if (gameMode != EditGame) return;
10684         }
10685         ResurrectChessProgram();
10686         SendToProgram("analyze\n", &first);
10687         first.analyzing = TRUE;
10688         /*first.maybeThinking = TRUE;*/
10689         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10690         EngineOutputPopUp();
10691     }
10692     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10693     pausing = FALSE;
10694     ModeHighlight();
10695     SetGameInfo();
10696
10697     StartAnalysisClock();
10698     GetTimeMark(&lastNodeCountTime);
10699     lastNodeCount = 0;
10700 }
10701
10702 void
10703 AnalyzeFileEvent()
10704 {
10705     if (appData.noChessProgram || gameMode == AnalyzeFile)
10706       return;
10707
10708     if (gameMode != AnalyzeMode) {
10709         EditGameEvent();
10710         if (gameMode != EditGame) return;
10711         ResurrectChessProgram();
10712         SendToProgram("analyze\n", &first);
10713         first.analyzing = TRUE;
10714         /*first.maybeThinking = TRUE;*/
10715         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10716         EngineOutputPopUp();
10717     }
10718     gameMode = AnalyzeFile;
10719     pausing = FALSE;
10720     ModeHighlight();
10721     SetGameInfo();
10722
10723     StartAnalysisClock();
10724     GetTimeMark(&lastNodeCountTime);
10725     lastNodeCount = 0;
10726 }
10727
10728 void
10729 MachineWhiteEvent()
10730 {
10731     char buf[MSG_SIZ];
10732     char *bookHit = NULL;
10733
10734     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10735       return;
10736
10737
10738     if (gameMode == PlayFromGameFile || 
10739         gameMode == TwoMachinesPlay  || 
10740         gameMode == Training         || 
10741         gameMode == AnalyzeMode      || 
10742         gameMode == EndOfGame)
10743         EditGameEvent();
10744
10745     if (gameMode == EditPosition) 
10746         EditPositionDone(TRUE);
10747
10748     if (!WhiteOnMove(currentMove)) {
10749         DisplayError(_("It is not White's turn"), 0);
10750         return;
10751     }
10752   
10753     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10754       ExitAnalyzeMode();
10755
10756     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10757         gameMode == AnalyzeFile)
10758         TruncateGame();
10759
10760     ResurrectChessProgram();    /* in case it isn't running */
10761     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10762         gameMode = MachinePlaysWhite;
10763         ResetClocks();
10764     } else
10765     gameMode = MachinePlaysWhite;
10766     pausing = FALSE;
10767     ModeHighlight();
10768     SetGameInfo();
10769     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10770     DisplayTitle(buf);
10771     if (first.sendName) {
10772       sprintf(buf, "name %s\n", gameInfo.black);
10773       SendToProgram(buf, &first);
10774     }
10775     if (first.sendTime) {
10776       if (first.useColors) {
10777         SendToProgram("black\n", &first); /*gnu kludge*/
10778       }
10779       SendTimeRemaining(&first, TRUE);
10780     }
10781     if (first.useColors) {
10782       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10783     }
10784     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10785     SetMachineThinkingEnables();
10786     first.maybeThinking = TRUE;
10787     StartClocks();
10788     firstMove = FALSE;
10789
10790     if (appData.autoFlipView && !flipView) {
10791       flipView = !flipView;
10792       DrawPosition(FALSE, NULL);
10793       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10794     }
10795
10796     if(bookHit) { // [HGM] book: simulate book reply
10797         static char bookMove[MSG_SIZ]; // a bit generous?
10798
10799         programStats.nodes = programStats.depth = programStats.time = 
10800         programStats.score = programStats.got_only_move = 0;
10801         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10802
10803         strcpy(bookMove, "move ");
10804         strcat(bookMove, bookHit);
10805         HandleMachineMove(bookMove, &first);
10806     }
10807 }
10808
10809 void
10810 MachineBlackEvent()
10811 {
10812     char buf[MSG_SIZ];
10813    char *bookHit = NULL;
10814
10815     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10816         return;
10817
10818
10819     if (gameMode == PlayFromGameFile || 
10820         gameMode == TwoMachinesPlay  || 
10821         gameMode == Training         || 
10822         gameMode == AnalyzeMode      || 
10823         gameMode == EndOfGame)
10824         EditGameEvent();
10825
10826     if (gameMode == EditPosition) 
10827         EditPositionDone(TRUE);
10828
10829     if (WhiteOnMove(currentMove)) {
10830         DisplayError(_("It is not Black's turn"), 0);
10831         return;
10832     }
10833     
10834     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10835       ExitAnalyzeMode();
10836
10837     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10838         gameMode == AnalyzeFile)
10839         TruncateGame();
10840
10841     ResurrectChessProgram();    /* in case it isn't running */
10842     gameMode = MachinePlaysBlack;
10843     pausing = FALSE;
10844     ModeHighlight();
10845     SetGameInfo();
10846     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10847     DisplayTitle(buf);
10848     if (first.sendName) {
10849       sprintf(buf, "name %s\n", gameInfo.white);
10850       SendToProgram(buf, &first);
10851     }
10852     if (first.sendTime) {
10853       if (first.useColors) {
10854         SendToProgram("white\n", &first); /*gnu kludge*/
10855       }
10856       SendTimeRemaining(&first, FALSE);
10857     }
10858     if (first.useColors) {
10859       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10860     }
10861     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10862     SetMachineThinkingEnables();
10863     first.maybeThinking = TRUE;
10864     StartClocks();
10865
10866     if (appData.autoFlipView && flipView) {
10867       flipView = !flipView;
10868       DrawPosition(FALSE, NULL);
10869       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10870     }
10871     if(bookHit) { // [HGM] book: simulate book reply
10872         static char bookMove[MSG_SIZ]; // a bit generous?
10873
10874         programStats.nodes = programStats.depth = programStats.time = 
10875         programStats.score = programStats.got_only_move = 0;
10876         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10877
10878         strcpy(bookMove, "move ");
10879         strcat(bookMove, bookHit);
10880         HandleMachineMove(bookMove, &first);
10881     }
10882 }
10883
10884
10885 void
10886 DisplayTwoMachinesTitle()
10887 {
10888     char buf[MSG_SIZ];
10889     if (appData.matchGames > 0) {
10890         if (first.twoMachinesColor[0] == 'w') {
10891             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10892                     gameInfo.white, gameInfo.black,
10893                     first.matchWins, second.matchWins,
10894                     matchGame - 1 - (first.matchWins + second.matchWins));
10895         } else {
10896             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10897                     gameInfo.white, gameInfo.black,
10898                     second.matchWins, first.matchWins,
10899                     matchGame - 1 - (first.matchWins + second.matchWins));
10900         }
10901     } else {
10902         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10903     }
10904     DisplayTitle(buf);
10905 }
10906
10907 void
10908 TwoMachinesEvent P((void))
10909 {
10910     int i;
10911     char buf[MSG_SIZ];
10912     ChessProgramState *onmove;
10913     char *bookHit = NULL;
10914     
10915     if (appData.noChessProgram) return;
10916
10917     switch (gameMode) {
10918       case TwoMachinesPlay:
10919         return;
10920       case MachinePlaysWhite:
10921       case MachinePlaysBlack:
10922         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10923             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10924             return;
10925         }
10926         /* fall through */
10927       case BeginningOfGame:
10928       case PlayFromGameFile:
10929       case EndOfGame:
10930         EditGameEvent();
10931         if (gameMode != EditGame) return;
10932         break;
10933       case EditPosition:
10934         EditPositionDone(TRUE);
10935         break;
10936       case AnalyzeMode:
10937       case AnalyzeFile:
10938         ExitAnalyzeMode();
10939         break;
10940       case EditGame:
10941       default:
10942         break;
10943     }
10944
10945     forwardMostMove = currentMove;
10946     ResurrectChessProgram();    /* in case first program isn't running */
10947
10948     if (second.pr == NULL) {
10949         StartChessProgram(&second);
10950         if (second.protocolVersion == 1) {
10951           TwoMachinesEventIfReady();
10952         } else {
10953           /* kludge: allow timeout for initial "feature" command */
10954           FreezeUI();
10955           DisplayMessage("", _("Starting second chess program"));
10956           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10957         }
10958         return;
10959     }
10960     DisplayMessage("", "");
10961     InitChessProgram(&second, FALSE);
10962     SendToProgram("force\n", &second);
10963     if (startedFromSetupPosition) {
10964         SendBoard(&second, backwardMostMove);
10965     if (appData.debugMode) {
10966         fprintf(debugFP, "Two Machines\n");
10967     }
10968     }
10969     for (i = backwardMostMove; i < forwardMostMove; i++) {
10970         SendMoveToProgram(i, &second);
10971     }
10972
10973     gameMode = TwoMachinesPlay;
10974     pausing = FALSE;
10975     ModeHighlight();
10976     SetGameInfo();
10977     DisplayTwoMachinesTitle();
10978     firstMove = TRUE;
10979     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10980         onmove = &first;
10981     } else {
10982         onmove = &second;
10983     }
10984
10985     SendToProgram(first.computerString, &first);
10986     if (first.sendName) {
10987       sprintf(buf, "name %s\n", second.tidy);
10988       SendToProgram(buf, &first);
10989     }
10990     SendToProgram(second.computerString, &second);
10991     if (second.sendName) {
10992       sprintf(buf, "name %s\n", first.tidy);
10993       SendToProgram(buf, &second);
10994     }
10995
10996     ResetClocks();
10997     if (!first.sendTime || !second.sendTime) {
10998         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10999         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11000     }
11001     if (onmove->sendTime) {
11002       if (onmove->useColors) {
11003         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11004       }
11005       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11006     }
11007     if (onmove->useColors) {
11008       SendToProgram(onmove->twoMachinesColor, onmove);
11009     }
11010     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11011 //    SendToProgram("go\n", onmove);
11012     onmove->maybeThinking = TRUE;
11013     SetMachineThinkingEnables();
11014
11015     StartClocks();
11016
11017     if(bookHit) { // [HGM] book: simulate book reply
11018         static char bookMove[MSG_SIZ]; // a bit generous?
11019
11020         programStats.nodes = programStats.depth = programStats.time = 
11021         programStats.score = programStats.got_only_move = 0;
11022         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11023
11024         strcpy(bookMove, "move ");
11025         strcat(bookMove, bookHit);
11026         savedMessage = bookMove; // args for deferred call
11027         savedState = onmove;
11028         ScheduleDelayedEvent(DeferredBookMove, 1);
11029     }
11030 }
11031
11032 void
11033 TrainingEvent()
11034 {
11035     if (gameMode == Training) {
11036       SetTrainingModeOff();
11037       gameMode = PlayFromGameFile;
11038       DisplayMessage("", _("Training mode off"));
11039     } else {
11040       gameMode = Training;
11041       animateTraining = appData.animate;
11042
11043       /* make sure we are not already at the end of the game */
11044       if (currentMove < forwardMostMove) {
11045         SetTrainingModeOn();
11046         DisplayMessage("", _("Training mode on"));
11047       } else {
11048         gameMode = PlayFromGameFile;
11049         DisplayError(_("Already at end of game"), 0);
11050       }
11051     }
11052     ModeHighlight();
11053 }
11054
11055 void
11056 IcsClientEvent()
11057 {
11058     if (!appData.icsActive) return;
11059     switch (gameMode) {
11060       case IcsPlayingWhite:
11061       case IcsPlayingBlack:
11062       case IcsObserving:
11063       case IcsIdle:
11064       case BeginningOfGame:
11065       case IcsExamining:
11066         return;
11067
11068       case EditGame:
11069         break;
11070
11071       case EditPosition:
11072         EditPositionDone(TRUE);
11073         break;
11074
11075       case AnalyzeMode:
11076       case AnalyzeFile:
11077         ExitAnalyzeMode();
11078         break;
11079         
11080       default:
11081         EditGameEvent();
11082         break;
11083     }
11084
11085     gameMode = IcsIdle;
11086     ModeHighlight();
11087     return;
11088 }
11089
11090
11091 void
11092 EditGameEvent()
11093 {
11094     int i;
11095
11096     switch (gameMode) {
11097       case Training:
11098         SetTrainingModeOff();
11099         break;
11100       case MachinePlaysWhite:
11101       case MachinePlaysBlack:
11102       case BeginningOfGame:
11103         SendToProgram("force\n", &first);
11104         SetUserThinkingEnables();
11105         break;
11106       case PlayFromGameFile:
11107         (void) StopLoadGameTimer();
11108         if (gameFileFP != NULL) {
11109             gameFileFP = NULL;
11110         }
11111         break;
11112       case EditPosition:
11113         EditPositionDone(TRUE);
11114         break;
11115       case AnalyzeMode:
11116       case AnalyzeFile:
11117         ExitAnalyzeMode();
11118         SendToProgram("force\n", &first);
11119         break;
11120       case TwoMachinesPlay:
11121         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11122         ResurrectChessProgram();
11123         SetUserThinkingEnables();
11124         break;
11125       case EndOfGame:
11126         ResurrectChessProgram();
11127         break;
11128       case IcsPlayingBlack:
11129       case IcsPlayingWhite:
11130         DisplayError(_("Warning: You are still playing a game"), 0);
11131         break;
11132       case IcsObserving:
11133         DisplayError(_("Warning: You are still observing a game"), 0);
11134         break;
11135       case IcsExamining:
11136         DisplayError(_("Warning: You are still examining a game"), 0);
11137         break;
11138       case IcsIdle:
11139         break;
11140       case EditGame:
11141       default:
11142         return;
11143     }
11144     
11145     pausing = FALSE;
11146     StopClocks();
11147     first.offeredDraw = second.offeredDraw = 0;
11148
11149     if (gameMode == PlayFromGameFile) {
11150         whiteTimeRemaining = timeRemaining[0][currentMove];
11151         blackTimeRemaining = timeRemaining[1][currentMove];
11152         DisplayTitle("");
11153     }
11154
11155     if (gameMode == MachinePlaysWhite ||
11156         gameMode == MachinePlaysBlack ||
11157         gameMode == TwoMachinesPlay ||
11158         gameMode == EndOfGame) {
11159         i = forwardMostMove;
11160         while (i > currentMove) {
11161             SendToProgram("undo\n", &first);
11162             i--;
11163         }
11164         whiteTimeRemaining = timeRemaining[0][currentMove];
11165         blackTimeRemaining = timeRemaining[1][currentMove];
11166         DisplayBothClocks();
11167         if (whiteFlag || blackFlag) {
11168             whiteFlag = blackFlag = 0;
11169         }
11170         DisplayTitle("");
11171     }           
11172     
11173     gameMode = EditGame;
11174     ModeHighlight();
11175     SetGameInfo();
11176 }
11177
11178
11179 void
11180 EditPositionEvent()
11181 {
11182     if (gameMode == EditPosition) {
11183         EditGameEvent();
11184         return;
11185     }
11186     
11187     EditGameEvent();
11188     if (gameMode != EditGame) return;
11189     
11190     gameMode = EditPosition;
11191     ModeHighlight();
11192     SetGameInfo();
11193     if (currentMove > 0)
11194       CopyBoard(boards[0], boards[currentMove]);
11195     
11196     blackPlaysFirst = !WhiteOnMove(currentMove);
11197     ResetClocks();
11198     currentMove = forwardMostMove = backwardMostMove = 0;
11199     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11200     DisplayMove(-1);
11201 }
11202
11203 void
11204 ExitAnalyzeMode()
11205 {
11206     /* [DM] icsEngineAnalyze - possible call from other functions */
11207     if (appData.icsEngineAnalyze) {
11208         appData.icsEngineAnalyze = FALSE;
11209
11210         DisplayMessage("",_("Close ICS engine analyze..."));
11211     }
11212     if (first.analysisSupport && first.analyzing) {
11213       SendToProgram("exit\n", &first);
11214       first.analyzing = FALSE;
11215     }
11216     thinkOutput[0] = NULLCHAR;
11217 }
11218
11219 void
11220 EditPositionDone(Boolean fakeRights)
11221 {
11222     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11223
11224     startedFromSetupPosition = TRUE;
11225     InitChessProgram(&first, FALSE);
11226     if(fakeRights)  
11227       { /* don't do this if we just pasted FEN */
11228         castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11229         if(boards[0][0][BOARD_WIDTH>>1] == king) 
11230           {
11231             castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11232             castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11233           } 
11234         else 
11235           castlingRights[0][2] = -1;
11236         if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) 
11237           {
11238             castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11239             castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11240           } 
11241         else 
11242           castlingRights[0][5] = -1;
11243       }
11244     SendToProgram("force\n", &first);
11245     if (blackPlaysFirst) {
11246         strcpy(moveList[0], "");
11247         strcpy(parseList[0], "");
11248         currentMove = forwardMostMove = backwardMostMove = 1;
11249         CopyBoard(boards[1], boards[0]);
11250         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11251         { int i;
11252           epStatus[1] = epStatus[0];
11253           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11254         }
11255     } else {
11256         currentMove = forwardMostMove = backwardMostMove = 0;
11257     }
11258     SendBoard(&first, forwardMostMove);
11259     if (appData.debugMode) {
11260         fprintf(debugFP, "EditPosDone\n");
11261     }
11262     DisplayTitle("");
11263     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11264     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11265     gameMode = EditGame;
11266     ModeHighlight();
11267     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11268     ClearHighlights(); /* [AS] */
11269 }
11270
11271 /* Pause for `ms' milliseconds */
11272 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11273 void
11274 TimeDelay(ms)
11275      long ms;
11276 {
11277     TimeMark m1, m2;
11278
11279     GetTimeMark(&m1);
11280     do {
11281         GetTimeMark(&m2);
11282     } while (SubtractTimeMarks(&m2, &m1) < ms);
11283 }
11284
11285 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11286 void
11287 SendMultiLineToICS(buf)
11288      char *buf;
11289 {
11290     char temp[MSG_SIZ+1], *p;
11291     int len;
11292
11293     len = strlen(buf);
11294     if (len > MSG_SIZ)
11295       len = MSG_SIZ;
11296   
11297     strncpy(temp, buf, len);
11298     temp[len] = 0;
11299
11300     p = temp;
11301     while (*p) {
11302         if (*p == '\n' || *p == '\r')
11303           *p = ' ';
11304         ++p;
11305     }
11306
11307     strcat(temp, "\n");
11308     SendToICS(temp);
11309     SendToPlayer(temp, strlen(temp));
11310 }
11311
11312 void
11313 SetWhiteToPlayEvent()
11314 {
11315     if (gameMode == EditPosition) {
11316         blackPlaysFirst = FALSE;
11317         DisplayBothClocks();    /* works because currentMove is 0 */
11318     } else if (gameMode == IcsExamining) {
11319         SendToICS(ics_prefix);
11320         SendToICS("tomove white\n");
11321     }
11322 }
11323
11324 void
11325 SetBlackToPlayEvent()
11326 {
11327     if (gameMode == EditPosition) {
11328         blackPlaysFirst = TRUE;
11329         currentMove = 1;        /* kludge */
11330         DisplayBothClocks();
11331         currentMove = 0;
11332     } else if (gameMode == IcsExamining) {
11333         SendToICS(ics_prefix);
11334         SendToICS("tomove black\n");
11335     }
11336 }
11337
11338 void
11339 EditPositionMenuEvent(selection, x, y)
11340      ChessSquare selection;
11341      int x, y;
11342 {
11343     char buf[MSG_SIZ];
11344     ChessSquare piece = boards[0][y][x];
11345
11346     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11347
11348     switch (selection) {
11349       case ClearBoard:
11350         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11351             SendToICS(ics_prefix);
11352             SendToICS("bsetup clear\n");
11353         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11354             SendToICS(ics_prefix);
11355             SendToICS("clearboard\n");
11356         } else {
11357             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11358                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11359                 for (y = 0; y < BOARD_HEIGHT; y++) {
11360                     if (gameMode == IcsExamining) {
11361                         if (boards[currentMove][y][x] != EmptySquare) {
11362                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11363                                     AAA + x, ONE + y);
11364                             SendToICS(buf);
11365                         }
11366                     } else {
11367                         boards[0][y][x] = p;
11368                     }
11369                 }
11370             }
11371         }
11372         if (gameMode == EditPosition) {
11373             DrawPosition(FALSE, boards[0]);
11374         }
11375         break;
11376
11377       case WhitePlay:
11378         SetWhiteToPlayEvent();
11379         break;
11380
11381       case BlackPlay:
11382         SetBlackToPlayEvent();
11383         break;
11384
11385       case EmptySquare:
11386         if (gameMode == IcsExamining) {
11387             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11388             SendToICS(buf);
11389         } else {
11390             boards[0][y][x] = EmptySquare;
11391             DrawPosition(FALSE, boards[0]);
11392         }
11393         break;
11394
11395       case PromotePiece:
11396         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11397            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11398             selection = (ChessSquare) (PROMOTED piece);
11399         } else if(piece == EmptySquare) selection = WhiteSilver;
11400         else selection = (ChessSquare)((int)piece - 1);
11401         goto defaultlabel;
11402
11403       case DemotePiece:
11404         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11405            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11406             selection = (ChessSquare) (DEMOTED piece);
11407         } else if(piece == EmptySquare) selection = BlackSilver;
11408         else selection = (ChessSquare)((int)piece + 1);       
11409         goto defaultlabel;
11410
11411       case WhiteQueen:
11412       case BlackQueen:
11413         if(gameInfo.variant == VariantShatranj ||
11414            gameInfo.variant == VariantXiangqi  ||
11415            gameInfo.variant == VariantCourier  ||
11416            gameInfo.variant == VariantMakruk     )
11417             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11418         goto defaultlabel;
11419
11420       case WhiteKing:
11421       case BlackKing:
11422         if(gameInfo.variant == VariantXiangqi)
11423             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11424         if(gameInfo.variant == VariantKnightmate)
11425             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11426       default:
11427         defaultlabel:
11428         if (gameMode == IcsExamining) {
11429             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11430                     PieceToChar(selection), AAA + x, ONE + y);
11431             SendToICS(buf);
11432         } else {
11433             boards[0][y][x] = selection;
11434             DrawPosition(FALSE, boards[0]);
11435         }
11436         break;
11437     }
11438 }
11439
11440
11441 void
11442 DropMenuEvent(selection, x, y)
11443      ChessSquare selection;
11444      int x, y;
11445 {
11446     ChessMove moveType;
11447
11448     switch (gameMode) {
11449       case IcsPlayingWhite:
11450       case MachinePlaysBlack:
11451         if (!WhiteOnMove(currentMove)) {
11452             DisplayMoveError(_("It is Black's turn"));
11453             return;
11454         }
11455         moveType = WhiteDrop;
11456         break;
11457       case IcsPlayingBlack:
11458       case MachinePlaysWhite:
11459         if (WhiteOnMove(currentMove)) {
11460             DisplayMoveError(_("It is White's turn"));
11461             return;
11462         }
11463         moveType = BlackDrop;
11464         break;
11465       case EditGame:
11466         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11467         break;
11468       default:
11469         return;
11470     }
11471
11472     if (moveType == BlackDrop && selection < BlackPawn) {
11473       selection = (ChessSquare) ((int) selection
11474                                  + (int) BlackPawn - (int) WhitePawn);
11475     }
11476     if (boards[currentMove][y][x] != EmptySquare) {
11477         DisplayMoveError(_("That square is occupied"));
11478         return;
11479     }
11480
11481     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11482 }
11483
11484 void
11485 AcceptEvent()
11486 {
11487     /* Accept a pending offer of any kind from opponent */
11488     
11489     if (appData.icsActive) {
11490         SendToICS(ics_prefix);
11491         SendToICS("accept\n");
11492     } else if (cmailMsgLoaded) {
11493         if (currentMove == cmailOldMove &&
11494             commentList[cmailOldMove] != NULL &&
11495             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11496                    "Black offers a draw" : "White offers a draw")) {
11497             TruncateGame();
11498             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11499             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11500         } else {
11501             DisplayError(_("There is no pending offer on this move"), 0);
11502             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11503         }
11504     } else {
11505         /* Not used for offers from chess program */
11506     }
11507 }
11508
11509 void
11510 DeclineEvent()
11511 {
11512     /* Decline a pending offer of any kind from opponent */
11513     
11514     if (appData.icsActive) {
11515         SendToICS(ics_prefix);
11516         SendToICS("decline\n");
11517     } else if (cmailMsgLoaded) {
11518         if (currentMove == cmailOldMove &&
11519             commentList[cmailOldMove] != NULL &&
11520             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11521                    "Black offers a draw" : "White offers a draw")) {
11522 #ifdef NOTDEF
11523             AppendComment(cmailOldMove, "Draw declined");
11524             DisplayComment(cmailOldMove - 1, "Draw declined");
11525 #endif /*NOTDEF*/
11526         } else {
11527             DisplayError(_("There is no pending offer on this move"), 0);
11528         }
11529     } else {
11530         /* Not used for offers from chess program */
11531     }
11532 }
11533
11534 void
11535 RematchEvent()
11536 {
11537     /* Issue ICS rematch command */
11538     if (appData.icsActive) {
11539         SendToICS(ics_prefix);
11540         SendToICS("rematch\n");
11541     }
11542 }
11543
11544 void
11545 CallFlagEvent()
11546 {
11547     /* Call your opponent's flag (claim a win on time) */
11548     if (appData.icsActive) {
11549         SendToICS(ics_prefix);
11550         SendToICS("flag\n");
11551     } else {
11552         switch (gameMode) {
11553           default:
11554             return;
11555           case MachinePlaysWhite:
11556             if (whiteFlag) {
11557                 if (blackFlag)
11558                   GameEnds(GameIsDrawn, "Both players ran out of time",
11559                            GE_PLAYER);
11560                 else
11561                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11562             } else {
11563                 DisplayError(_("Your opponent is not out of time"), 0);
11564             }
11565             break;
11566           case MachinePlaysBlack:
11567             if (blackFlag) {
11568                 if (whiteFlag)
11569                   GameEnds(GameIsDrawn, "Both players ran out of time",
11570                            GE_PLAYER);
11571                 else
11572                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11573             } else {
11574                 DisplayError(_("Your opponent is not out of time"), 0);
11575             }
11576             break;
11577         }
11578     }
11579 }
11580
11581 void
11582 DrawEvent()
11583 {
11584     /* Offer draw or accept pending draw offer from opponent */
11585     
11586     if (appData.icsActive) {
11587         /* Note: tournament rules require draw offers to be
11588            made after you make your move but before you punch
11589            your clock.  Currently ICS doesn't let you do that;
11590            instead, you immediately punch your clock after making
11591            a move, but you can offer a draw at any time. */
11592         
11593         SendToICS(ics_prefix);
11594         SendToICS("draw\n");
11595     } else if (cmailMsgLoaded) {
11596         if (currentMove == cmailOldMove &&
11597             commentList[cmailOldMove] != NULL &&
11598             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11599                    "Black offers a draw" : "White offers a draw")) {
11600             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11601             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11602         } else if (currentMove == cmailOldMove + 1) {
11603             char *offer = WhiteOnMove(cmailOldMove) ?
11604               "White offers a draw" : "Black offers a draw";
11605             AppendComment(currentMove, offer);
11606             DisplayComment(currentMove - 1, offer);
11607             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11608         } else {
11609             DisplayError(_("You must make your move before offering a draw"), 0);
11610             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11611         }
11612     } else if (first.offeredDraw) {
11613         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11614     } else {
11615         if (first.sendDrawOffers) {
11616             SendToProgram("draw\n", &first);
11617             userOfferedDraw = TRUE;
11618         }
11619     }
11620 }
11621
11622 void
11623 AdjournEvent()
11624 {
11625     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11626     
11627     if (appData.icsActive) {
11628         SendToICS(ics_prefix);
11629         SendToICS("adjourn\n");
11630     } else {
11631         /* Currently GNU Chess doesn't offer or accept Adjourns */
11632     }
11633 }
11634
11635
11636 void
11637 AbortEvent()
11638 {
11639     /* Offer Abort or accept pending Abort offer from opponent */
11640     
11641     if (appData.icsActive) {
11642         SendToICS(ics_prefix);
11643         SendToICS("abort\n");
11644     } else {
11645         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11646     }
11647 }
11648
11649 void
11650 ResignEvent()
11651 {
11652     /* Resign.  You can do this even if it's not your turn. */
11653     
11654     if (appData.icsActive) {
11655         SendToICS(ics_prefix);
11656         SendToICS("resign\n");
11657     } else {
11658         switch (gameMode) {
11659           case MachinePlaysWhite:
11660             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11661             break;
11662           case MachinePlaysBlack:
11663             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11664             break;
11665           case EditGame:
11666             if (cmailMsgLoaded) {
11667                 TruncateGame();
11668                 if (WhiteOnMove(cmailOldMove)) {
11669                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11670                 } else {
11671                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11672                 }
11673                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11674             }
11675             break;
11676           default:
11677             break;
11678         }
11679     }
11680 }
11681
11682
11683 void
11684 StopObservingEvent()
11685 {
11686     /* Stop observing current games */
11687     SendToICS(ics_prefix);
11688     SendToICS("unobserve\n");
11689 }
11690
11691 void
11692 StopExaminingEvent()
11693 {
11694     /* Stop observing current game */
11695     SendToICS(ics_prefix);
11696     SendToICS("unexamine\n");
11697 }
11698
11699 void
11700 ForwardInner(target)
11701      int target;
11702 {
11703     int limit;
11704
11705     if (appData.debugMode)
11706         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11707                 target, currentMove, forwardMostMove);
11708
11709     if (gameMode == EditPosition)
11710       return;
11711
11712     if (gameMode == PlayFromGameFile && !pausing)
11713       PauseEvent();
11714     
11715     if (gameMode == IcsExamining && pausing)
11716       limit = pauseExamForwardMostMove;
11717     else
11718       limit = forwardMostMove;
11719     
11720     if (target > limit) target = limit;
11721
11722     if (target > 0 && moveList[target - 1][0]) {
11723         int fromX, fromY, toX, toY;
11724         toX = moveList[target - 1][2] - AAA;
11725         toY = moveList[target - 1][3] - ONE;
11726         if (moveList[target - 1][1] == '@') {
11727             if (appData.highlightLastMove) {
11728                 SetHighlights(-1, -1, toX, toY);
11729             }
11730         } else {
11731             fromX = moveList[target - 1][0] - AAA;
11732             fromY = moveList[target - 1][1] - ONE;
11733             if (target == currentMove + 1) {
11734                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11735             }
11736             if (appData.highlightLastMove) {
11737                 SetHighlights(fromX, fromY, toX, toY);
11738             }
11739         }
11740     }
11741     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11742         gameMode == Training || gameMode == PlayFromGameFile || 
11743         gameMode == AnalyzeFile) {
11744         while (currentMove < target) {
11745             SendMoveToProgram(currentMove++, &first);
11746         }
11747     } else {
11748         currentMove = target;
11749     }
11750     
11751     if (gameMode == EditGame || gameMode == EndOfGame) {
11752         whiteTimeRemaining = timeRemaining[0][currentMove];
11753         blackTimeRemaining = timeRemaining[1][currentMove];
11754     }
11755     DisplayBothClocks();
11756     DisplayMove(currentMove - 1);
11757     DrawPosition(FALSE, boards[currentMove]);
11758     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11759     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11760         DisplayComment(currentMove - 1, commentList[currentMove]);
11761     }
11762 }
11763
11764
11765 void
11766 ForwardEvent()
11767 {
11768     if (gameMode == IcsExamining && !pausing) {
11769         SendToICS(ics_prefix);
11770         SendToICS("forward\n");
11771     } else {
11772         ForwardInner(currentMove + 1);
11773     }
11774 }
11775
11776 void
11777 ToEndEvent()
11778 {
11779     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11780         /* to optimze, we temporarily turn off analysis mode while we feed
11781          * the remaining moves to the engine. Otherwise we get analysis output
11782          * after each move.
11783          */ 
11784         if (first.analysisSupport) {
11785           SendToProgram("exit\nforce\n", &first);
11786           first.analyzing = FALSE;
11787         }
11788     }
11789         
11790     if (gameMode == IcsExamining && !pausing) {
11791         SendToICS(ics_prefix);
11792         SendToICS("forward 999999\n");
11793     } else {
11794         ForwardInner(forwardMostMove);
11795     }
11796
11797     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11798         /* we have fed all the moves, so reactivate analysis mode */
11799         SendToProgram("analyze\n", &first);
11800         first.analyzing = TRUE;
11801         /*first.maybeThinking = TRUE;*/
11802         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11803     }
11804 }
11805
11806 void
11807 BackwardInner(target)
11808      int target;
11809 {
11810     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11811
11812     if (appData.debugMode)
11813         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11814                 target, currentMove, forwardMostMove);
11815
11816     if (gameMode == EditPosition) return;
11817     if (currentMove <= backwardMostMove) {
11818         ClearHighlights();
11819         DrawPosition(full_redraw, boards[currentMove]);
11820         return;
11821     }
11822     if (gameMode == PlayFromGameFile && !pausing)
11823       PauseEvent();
11824     
11825     if (moveList[target][0]) {
11826         int fromX, fromY, toX, toY;
11827         toX = moveList[target][2] - AAA;
11828         toY = moveList[target][3] - ONE;
11829         if (moveList[target][1] == '@') {
11830             if (appData.highlightLastMove) {
11831                 SetHighlights(-1, -1, toX, toY);
11832             }
11833         } else {
11834             fromX = moveList[target][0] - AAA;
11835             fromY = moveList[target][1] - ONE;
11836             if (target == currentMove - 1) {
11837                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11838             }
11839             if (appData.highlightLastMove) {
11840                 SetHighlights(fromX, fromY, toX, toY);
11841             }
11842         }
11843     }
11844     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11845         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11846         while (currentMove > target) {
11847             SendToProgram("undo\n", &first);
11848             currentMove--;
11849         }
11850     } else {
11851         currentMove = target;
11852     }
11853     
11854     if (gameMode == EditGame || gameMode == EndOfGame) {
11855         whiteTimeRemaining = timeRemaining[0][currentMove];
11856         blackTimeRemaining = timeRemaining[1][currentMove];
11857     }
11858     DisplayBothClocks();
11859     DisplayMove(currentMove - 1);
11860     DrawPosition(full_redraw, boards[currentMove]);
11861     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11862     // [HGM] PV info: routine tests if comment empty
11863     DisplayComment(currentMove - 1, commentList[currentMove]);
11864 }
11865
11866 void
11867 BackwardEvent()
11868 {
11869     if (gameMode == IcsExamining && !pausing) {
11870         SendToICS(ics_prefix);
11871         SendToICS("backward\n");
11872     } else {
11873         BackwardInner(currentMove - 1);
11874     }
11875 }
11876
11877 void
11878 ToStartEvent()
11879 {
11880     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11881         /* to optimize, we temporarily turn off analysis mode while we undo
11882          * all the moves. Otherwise we get analysis output after each undo.
11883          */ 
11884         if (first.analysisSupport) {
11885           SendToProgram("exit\nforce\n", &first);
11886           first.analyzing = FALSE;
11887         }
11888     }
11889
11890     if (gameMode == IcsExamining && !pausing) {
11891         SendToICS(ics_prefix);
11892         SendToICS("backward 999999\n");
11893     } else {
11894         BackwardInner(backwardMostMove);
11895     }
11896
11897     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11898         /* we have fed all the moves, so reactivate analysis mode */
11899         SendToProgram("analyze\n", &first);
11900         first.analyzing = TRUE;
11901         /*first.maybeThinking = TRUE;*/
11902         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11903     }
11904 }
11905
11906 void
11907 ToNrEvent(int to)
11908 {
11909   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11910   if (to >= forwardMostMove) to = forwardMostMove;
11911   if (to <= backwardMostMove) to = backwardMostMove;
11912   if (to < currentMove) {
11913     BackwardInner(to);
11914   } else {
11915     ForwardInner(to);
11916   }
11917 }
11918
11919 void
11920 RevertEvent()
11921 {
11922     if (gameMode != IcsExamining) {
11923         DisplayError(_("You are not examining a game"), 0);
11924         return;
11925     }
11926     if (pausing) {
11927         DisplayError(_("You can't revert while pausing"), 0);
11928         return;
11929     }
11930     SendToICS(ics_prefix);
11931     SendToICS("revert\n");
11932 }
11933
11934 void
11935 RetractMoveEvent()
11936 {
11937     switch (gameMode) {
11938       case MachinePlaysWhite:
11939       case MachinePlaysBlack:
11940         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11941             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11942             return;
11943         }
11944         if (forwardMostMove < 2) return;
11945         currentMove = forwardMostMove = forwardMostMove - 2;
11946         whiteTimeRemaining = timeRemaining[0][currentMove];
11947         blackTimeRemaining = timeRemaining[1][currentMove];
11948         DisplayBothClocks();
11949         DisplayMove(currentMove - 1);
11950         ClearHighlights();/*!! could figure this out*/
11951         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11952         SendToProgram("remove\n", &first);
11953         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11954         break;
11955
11956       case BeginningOfGame:
11957       default:
11958         break;
11959
11960       case IcsPlayingWhite:
11961       case IcsPlayingBlack:
11962         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11963             SendToICS(ics_prefix);
11964             SendToICS("takeback 2\n");
11965         } else {
11966             SendToICS(ics_prefix);
11967             SendToICS("takeback 1\n");
11968         }
11969         break;
11970     }
11971 }
11972
11973 void
11974 MoveNowEvent()
11975 {
11976     ChessProgramState *cps;
11977
11978     switch (gameMode) {
11979       case MachinePlaysWhite:
11980         if (!WhiteOnMove(forwardMostMove)) {
11981             DisplayError(_("It is your turn"), 0);
11982             return;
11983         }
11984         cps = &first;
11985         break;
11986       case MachinePlaysBlack:
11987         if (WhiteOnMove(forwardMostMove)) {
11988             DisplayError(_("It is your turn"), 0);
11989             return;
11990         }
11991         cps = &first;
11992         break;
11993       case TwoMachinesPlay:
11994         if (WhiteOnMove(forwardMostMove) ==
11995             (first.twoMachinesColor[0] == 'w')) {
11996             cps = &first;
11997         } else {
11998             cps = &second;
11999         }
12000         break;
12001       case BeginningOfGame:
12002       default:
12003         return;
12004     }
12005     SendToProgram("?\n", cps);
12006 }
12007
12008 void
12009 TruncateGameEvent()
12010 {
12011     EditGameEvent();
12012     if (gameMode != EditGame) return;
12013     TruncateGame();
12014 }
12015
12016 void
12017 TruncateGame()
12018 {
12019     if (forwardMostMove > currentMove) {
12020         if (gameInfo.resultDetails != NULL) {
12021             free(gameInfo.resultDetails);
12022             gameInfo.resultDetails = NULL;
12023             gameInfo.result = GameUnfinished;
12024         }
12025         forwardMostMove = currentMove;
12026         HistorySet(parseList, backwardMostMove, forwardMostMove,
12027                    currentMove-1);
12028     }
12029 }
12030
12031 void
12032 HintEvent()
12033 {
12034     if (appData.noChessProgram) return;
12035     switch (gameMode) {
12036       case MachinePlaysWhite:
12037         if (WhiteOnMove(forwardMostMove)) {
12038             DisplayError(_("Wait until your turn"), 0);
12039             return;
12040         }
12041         break;
12042       case BeginningOfGame:
12043       case MachinePlaysBlack:
12044         if (!WhiteOnMove(forwardMostMove)) {
12045             DisplayError(_("Wait until your turn"), 0);
12046             return;
12047         }
12048         break;
12049       default:
12050         DisplayError(_("No hint available"), 0);
12051         return;
12052     }
12053     SendToProgram("hint\n", &first);
12054     hintRequested = TRUE;
12055 }
12056
12057 void
12058 BookEvent()
12059 {
12060     if (appData.noChessProgram) return;
12061     switch (gameMode) {
12062       case MachinePlaysWhite:
12063         if (WhiteOnMove(forwardMostMove)) {
12064             DisplayError(_("Wait until your turn"), 0);
12065             return;
12066         }
12067         break;
12068       case BeginningOfGame:
12069       case MachinePlaysBlack:
12070         if (!WhiteOnMove(forwardMostMove)) {
12071             DisplayError(_("Wait until your turn"), 0);
12072             return;
12073         }
12074         break;
12075       case EditPosition:
12076         EditPositionDone(TRUE);
12077         break;
12078       case TwoMachinesPlay:
12079         return;
12080       default:
12081         break;
12082     }
12083     SendToProgram("bk\n", &first);
12084     bookOutput[0] = NULLCHAR;
12085     bookRequested = TRUE;
12086 }
12087
12088 void
12089 AboutGameEvent()
12090 {
12091     char *tags = PGNTags(&gameInfo);
12092     TagsPopUp(tags, CmailMsg());
12093     free(tags);
12094 }
12095
12096 /* end button procedures */
12097
12098 void
12099 PrintPosition(fp, move)
12100      FILE *fp;
12101      int move;
12102 {
12103     int i, j;
12104     
12105     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12106         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12107             char c = PieceToChar(boards[move][i][j]);
12108             fputc(c == 'x' ? '.' : c, fp);
12109             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12110         }
12111     }
12112     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12113       fprintf(fp, "white to play\n");
12114     else
12115       fprintf(fp, "black to play\n");
12116 }
12117
12118 void
12119 PrintOpponents(fp)
12120      FILE *fp;
12121 {
12122     if (gameInfo.white != NULL) {
12123         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12124     } else {
12125         fprintf(fp, "\n");
12126     }
12127 }
12128
12129 /* Find last component of program's own name, using some heuristics */
12130 void
12131 TidyProgramName(prog, host, buf)
12132      char *prog, *host, buf[MSG_SIZ];
12133 {
12134     char *p, *q;
12135     int local = (strcmp(host, "localhost") == 0);
12136     while (!local && (p = strchr(prog, ';')) != NULL) {
12137         p++;
12138         while (*p == ' ') p++;
12139         prog = p;
12140     }
12141     if (*prog == '"' || *prog == '\'') {
12142         q = strchr(prog + 1, *prog);
12143     } else {
12144         q = strchr(prog, ' ');
12145     }
12146     if (q == NULL) q = prog + strlen(prog);
12147     p = q;
12148     while (p >= prog && *p != '/' && *p != '\\') p--;
12149     p++;
12150     if(p == prog && *p == '"') p++;
12151     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12152     memcpy(buf, p, q - p);
12153     buf[q - p] = NULLCHAR;
12154     if (!local) {
12155         strcat(buf, "@");
12156         strcat(buf, host);
12157     }
12158 }
12159
12160 char *
12161 TimeControlTagValue()
12162 {
12163     char buf[MSG_SIZ];
12164     if (!appData.clockMode) {
12165         strcpy(buf, "-");
12166     } else if (movesPerSession > 0) {
12167         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12168     } else if (timeIncrement == 0) {
12169         sprintf(buf, "%ld", timeControl/1000);
12170     } else {
12171         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12172     }
12173     return StrSave(buf);
12174 }
12175
12176 void
12177 SetGameInfo()
12178 {
12179     /* This routine is used only for certain modes */
12180     VariantClass v = gameInfo.variant;
12181     ClearGameInfo(&gameInfo);
12182     gameInfo.variant = v;
12183
12184     switch (gameMode) {
12185       case MachinePlaysWhite:
12186         gameInfo.event = StrSave( appData.pgnEventHeader );
12187         gameInfo.site = StrSave(HostName());
12188         gameInfo.date = PGNDate();
12189         gameInfo.round = StrSave("-");
12190         gameInfo.white = StrSave(first.tidy);
12191         gameInfo.black = StrSave(UserName());
12192         gameInfo.timeControl = TimeControlTagValue();
12193         break;
12194
12195       case MachinePlaysBlack:
12196         gameInfo.event = StrSave( appData.pgnEventHeader );
12197         gameInfo.site = StrSave(HostName());
12198         gameInfo.date = PGNDate();
12199         gameInfo.round = StrSave("-");
12200         gameInfo.white = StrSave(UserName());
12201         gameInfo.black = StrSave(first.tidy);
12202         gameInfo.timeControl = TimeControlTagValue();
12203         break;
12204
12205       case TwoMachinesPlay:
12206         gameInfo.event = StrSave( appData.pgnEventHeader );
12207         gameInfo.site = StrSave(HostName());
12208         gameInfo.date = PGNDate();
12209         if (matchGame > 0) {
12210             char buf[MSG_SIZ];
12211             sprintf(buf, "%d", matchGame);
12212             gameInfo.round = StrSave(buf);
12213         } else {
12214             gameInfo.round = StrSave("-");
12215         }
12216         if (first.twoMachinesColor[0] == 'w') {
12217             gameInfo.white = StrSave(first.tidy);
12218             gameInfo.black = StrSave(second.tidy);
12219         } else {
12220             gameInfo.white = StrSave(second.tidy);
12221             gameInfo.black = StrSave(first.tidy);
12222         }
12223         gameInfo.timeControl = TimeControlTagValue();
12224         break;
12225
12226       case EditGame:
12227         gameInfo.event = StrSave("Edited game");
12228         gameInfo.site = StrSave(HostName());
12229         gameInfo.date = PGNDate();
12230         gameInfo.round = StrSave("-");
12231         gameInfo.white = StrSave("-");
12232         gameInfo.black = StrSave("-");
12233         break;
12234
12235       case EditPosition:
12236         gameInfo.event = StrSave("Edited position");
12237         gameInfo.site = StrSave(HostName());
12238         gameInfo.date = PGNDate();
12239         gameInfo.round = StrSave("-");
12240         gameInfo.white = StrSave("-");
12241         gameInfo.black = StrSave("-");
12242         break;
12243
12244       case IcsPlayingWhite:
12245       case IcsPlayingBlack:
12246       case IcsObserving:
12247       case IcsExamining:
12248         break;
12249
12250       case PlayFromGameFile:
12251         gameInfo.event = StrSave("Game from non-PGN file");
12252         gameInfo.site = StrSave(HostName());
12253         gameInfo.date = PGNDate();
12254         gameInfo.round = StrSave("-");
12255         gameInfo.white = StrSave("?");
12256         gameInfo.black = StrSave("?");
12257         break;
12258
12259       default:
12260         break;
12261     }
12262 }
12263
12264 void
12265 ReplaceComment(index, text)
12266      int index;
12267      char *text;
12268 {
12269     int len;
12270
12271     while (*text == '\n') text++;
12272     len = strlen(text);
12273     while (len > 0 && text[len - 1] == '\n') len--;
12274
12275     if (commentList[index] != NULL)
12276       free(commentList[index]);
12277
12278     if (len == 0) {
12279         commentList[index] = NULL;
12280         return;
12281     }
12282     commentList[index] = (char *) malloc(len + 2);
12283     strncpy(commentList[index], text, len);
12284     commentList[index][len] = '\n';
12285     commentList[index][len + 1] = NULLCHAR;
12286 }
12287
12288 void
12289 CrushCRs(text)
12290      char *text;
12291 {
12292   char *p = text;
12293   char *q = text;
12294   char ch;
12295
12296   do {
12297     ch = *p++;
12298     if (ch == '\r') continue;
12299     *q++ = ch;
12300   } while (ch != '\0');
12301 }
12302
12303 void
12304 AppendComment(index, text)
12305      int index;
12306      char *text;
12307 {
12308     int oldlen, len;
12309     char *old;
12310
12311     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12312
12313     CrushCRs(text);
12314     while (*text == '\n') text++;
12315     len = strlen(text);
12316     while (len > 0 && text[len - 1] == '\n') len--;
12317
12318     if (len == 0) return;
12319
12320     if (commentList[index] != NULL) {
12321         old = commentList[index];
12322         oldlen = strlen(old);
12323         commentList[index] = (char *) malloc(oldlen + len + 2);
12324         strcpy(commentList[index], old);
12325         free(old);
12326         strncpy(&commentList[index][oldlen], text, len);
12327         commentList[index][oldlen + len] = '\n';
12328         commentList[index][oldlen + len + 1] = NULLCHAR;
12329     } else {
12330         commentList[index] = (char *) malloc(len + 2);
12331         strncpy(commentList[index], text, len);
12332         commentList[index][len] = '\n';
12333         commentList[index][len + 1] = NULLCHAR;
12334     }
12335 }
12336
12337 static char * FindStr( char * text, char * sub_text )
12338 {
12339     char * result = strstr( text, sub_text );
12340
12341     if( result != NULL ) {
12342         result += strlen( sub_text );
12343     }
12344
12345     return result;
12346 }
12347
12348 /* [AS] Try to extract PV info from PGN comment */
12349 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12350 char *GetInfoFromComment( int index, char * text )
12351 {
12352     char * sep = text;
12353
12354     if( text != NULL && index > 0 ) {
12355         int score = 0;
12356         int depth = 0;
12357         int time = -1, sec = 0, deci;
12358         char * s_eval = FindStr( text, "[%eval " );
12359         char * s_emt = FindStr( text, "[%emt " );
12360
12361         if( s_eval != NULL || s_emt != NULL ) {
12362             /* New style */
12363             char delim;
12364
12365             if( s_eval != NULL ) {
12366                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12367                     return text;
12368                 }
12369
12370                 if( delim != ']' ) {
12371                     return text;
12372                 }
12373             }
12374
12375             if( s_emt != NULL ) {
12376             }
12377         }
12378         else {
12379             /* We expect something like: [+|-]nnn.nn/dd */
12380             int score_lo = 0;
12381
12382             sep = strchr( text, '/' );
12383             if( sep == NULL || sep < (text+4) ) {
12384                 return text;
12385             }
12386
12387             time = -1; sec = -1; deci = -1;
12388             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12389                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12390                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12391                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12392                 return text;
12393             }
12394
12395             if( score_lo < 0 || score_lo >= 100 ) {
12396                 return text;
12397             }
12398
12399             if(sec >= 0) time = 600*time + 10*sec; else
12400             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12401
12402             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12403
12404             /* [HGM] PV time: now locate end of PV info */
12405             while( *++sep >= '0' && *sep <= '9'); // strip depth
12406             if(time >= 0)
12407             while( *++sep >= '0' && *sep <= '9'); // strip time
12408             if(sec >= 0)
12409             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12410             if(deci >= 0)
12411             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12412             while(*sep == ' ') sep++;
12413         }
12414
12415         if( depth <= 0 ) {
12416             return text;
12417         }
12418
12419         if( time < 0 ) {
12420             time = -1;
12421         }
12422
12423         pvInfoList[index-1].depth = depth;
12424         pvInfoList[index-1].score = score;
12425         pvInfoList[index-1].time  = 10*time; // centi-sec
12426     }
12427     return sep;
12428 }
12429
12430 void
12431 SendToProgram(message, cps)
12432      char *message;
12433      ChessProgramState *cps;
12434 {
12435     int count, outCount, error;
12436     char buf[MSG_SIZ];
12437
12438     if (cps->pr == NULL) return;
12439     Attention(cps);
12440     
12441     if (appData.debugMode) {
12442         TimeMark now;
12443         GetTimeMark(&now);
12444         fprintf(debugFP, "%ld >%-6s: %s", 
12445                 SubtractTimeMarks(&now, &programStartTime),
12446                 cps->which, message);
12447     }
12448     
12449     count = strlen(message);
12450     outCount = OutputToProcess(cps->pr, message, count, &error);
12451     if (outCount < count && !exiting 
12452                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12453         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12454         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12455             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12456                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12457                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12458             } else {
12459                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12460             }
12461             gameInfo.resultDetails = StrSave(buf);
12462         }
12463         DisplayFatalError(buf, error, 1);
12464     }
12465 }
12466
12467 void
12468 ReceiveFromProgram(isr, closure, message, count, error)
12469      InputSourceRef isr;
12470      VOIDSTAR closure;
12471      char *message;
12472      int count;
12473      int error;
12474 {
12475     char *end_str;
12476     char buf[MSG_SIZ];
12477     ChessProgramState *cps = (ChessProgramState *)closure;
12478
12479     if (isr != cps->isr) return; /* Killed intentionally */
12480     if (count <= 0) {
12481         if (count == 0) {
12482             sprintf(buf,
12483                     _("Error: %s chess program (%s) exited unexpectedly"),
12484                     cps->which, cps->program);
12485         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12486                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12487                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12488                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12489                 } else {
12490                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12491                 }
12492                 gameInfo.resultDetails = StrSave(buf);
12493             }
12494             RemoveInputSource(cps->isr);
12495             DisplayFatalError(buf, 0, 1);
12496         } else {
12497             sprintf(buf,
12498                     _("Error reading from %s chess program (%s)"),
12499                     cps->which, cps->program);
12500             RemoveInputSource(cps->isr);
12501
12502             /* [AS] Program is misbehaving badly... kill it */
12503             if( count == -2 ) {
12504                 DestroyChildProcess( cps->pr, 9 );
12505                 cps->pr = NoProc;
12506             }
12507
12508             DisplayFatalError(buf, error, 1);
12509         }
12510         return;
12511     }
12512     
12513     if ((end_str = strchr(message, '\r')) != NULL)
12514       *end_str = NULLCHAR;
12515     if ((end_str = strchr(message, '\n')) != NULL)
12516       *end_str = NULLCHAR;
12517     
12518     if (appData.debugMode) {
12519         TimeMark now; int print = 1;
12520         char *quote = ""; char c; int i;
12521
12522         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12523                 char start = message[0];
12524                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12525                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12526                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12527                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12528                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12529                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12530                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12531                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12532                         { quote = "# "; print = (appData.engineComments == 2); }
12533                 message[0] = start; // restore original message
12534         }
12535         if(print) {
12536                 GetTimeMark(&now);
12537                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12538                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12539                         quote,
12540                         message);
12541         }
12542     }
12543
12544     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12545     if (appData.icsEngineAnalyze) {
12546         if (strstr(message, "whisper") != NULL ||
12547              strstr(message, "kibitz") != NULL || 
12548             strstr(message, "tellics") != NULL) return;
12549     }
12550
12551     HandleMachineMove(message, cps);
12552 }
12553
12554
12555 void
12556 SendTimeControl(cps, mps, tc, inc, sd, st)
12557      ChessProgramState *cps;
12558      int mps, inc, sd, st;
12559      long tc;
12560 {
12561     char buf[MSG_SIZ];
12562     int seconds;
12563
12564     if( timeControl_2 > 0 ) {
12565         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12566             tc = timeControl_2;
12567         }
12568     }
12569     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12570     inc /= cps->timeOdds;
12571     st  /= cps->timeOdds;
12572
12573     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12574
12575     if (st > 0) {
12576       /* Set exact time per move, normally using st command */
12577       if (cps->stKludge) {
12578         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12579         seconds = st % 60;
12580         if (seconds == 0) {
12581           sprintf(buf, "level 1 %d\n", st/60);
12582         } else {
12583           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12584         }
12585       } else {
12586         sprintf(buf, "st %d\n", st);
12587       }
12588     } else {
12589       /* Set conventional or incremental time control, using level command */
12590       if (seconds == 0) {
12591         /* Note old gnuchess bug -- minutes:seconds used to not work.
12592            Fixed in later versions, but still avoid :seconds
12593            when seconds is 0. */
12594         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12595       } else {
12596         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12597                 seconds, inc/1000);
12598       }
12599     }
12600     SendToProgram(buf, cps);
12601
12602     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12603     /* Orthogonally, limit search to given depth */
12604     if (sd > 0) {
12605       if (cps->sdKludge) {
12606         sprintf(buf, "depth\n%d\n", sd);
12607       } else {
12608         sprintf(buf, "sd %d\n", sd);
12609       }
12610       SendToProgram(buf, cps);
12611     }
12612
12613     if(cps->nps > 0) { /* [HGM] nps */
12614         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12615         else {
12616                 sprintf(buf, "nps %d\n", cps->nps);
12617               SendToProgram(buf, cps);
12618         }
12619     }
12620 }
12621
12622 ChessProgramState *WhitePlayer()
12623 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12624 {
12625     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12626        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12627         return &second;
12628     return &first;
12629 }
12630
12631 void
12632 SendTimeRemaining(cps, machineWhite)
12633      ChessProgramState *cps;
12634      int /*boolean*/ machineWhite;
12635 {
12636     char message[MSG_SIZ];
12637     long time, otime;
12638
12639     /* Note: this routine must be called when the clocks are stopped
12640        or when they have *just* been set or switched; otherwise
12641        it will be off by the time since the current tick started.
12642     */
12643     if (machineWhite) {
12644         time = whiteTimeRemaining / 10;
12645         otime = blackTimeRemaining / 10;
12646     } else {
12647         time = blackTimeRemaining / 10;
12648         otime = whiteTimeRemaining / 10;
12649     }
12650     /* [HGM] translate opponent's time by time-odds factor */
12651     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12652     if (appData.debugMode) {
12653         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12654     }
12655
12656     if (time <= 0) time = 1;
12657     if (otime <= 0) otime = 1;
12658     
12659     sprintf(message, "time %ld\n", time);
12660     SendToProgram(message, cps);
12661
12662     sprintf(message, "otim %ld\n", otime);
12663     SendToProgram(message, cps);
12664 }
12665
12666 int
12667 BoolFeature(p, name, loc, cps)
12668      char **p;
12669      char *name;
12670      int *loc;
12671      ChessProgramState *cps;
12672 {
12673   char buf[MSG_SIZ];
12674   int len = strlen(name);
12675   int val;
12676   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12677     (*p) += len + 1;
12678     sscanf(*p, "%d", &val);
12679     *loc = (val != 0);
12680     while (**p && **p != ' ') (*p)++;
12681     sprintf(buf, "accepted %s\n", name);
12682     SendToProgram(buf, cps);
12683     return TRUE;
12684   }
12685   return FALSE;
12686 }
12687
12688 int
12689 IntFeature(p, name, loc, cps)
12690      char **p;
12691      char *name;
12692      int *loc;
12693      ChessProgramState *cps;
12694 {
12695   char buf[MSG_SIZ];
12696   int len = strlen(name);
12697   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12698     (*p) += len + 1;
12699     sscanf(*p, "%d", loc);
12700     while (**p && **p != ' ') (*p)++;
12701     sprintf(buf, "accepted %s\n", name);
12702     SendToProgram(buf, cps);
12703     return TRUE;
12704   }
12705   return FALSE;
12706 }
12707
12708 int
12709 StringFeature(p, name, loc, cps)
12710      char **p;
12711      char *name;
12712      char loc[];
12713      ChessProgramState *cps;
12714 {
12715   char buf[MSG_SIZ];
12716   int len = strlen(name);
12717   if (strncmp((*p), name, len) == 0
12718       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12719     (*p) += len + 2;
12720     sscanf(*p, "%[^\"]", loc);
12721     while (**p && **p != '\"') (*p)++;
12722     if (**p == '\"') (*p)++;
12723     sprintf(buf, "accepted %s\n", name);
12724     SendToProgram(buf, cps);
12725     return TRUE;
12726   }
12727   return FALSE;
12728 }
12729
12730 int 
12731 ParseOption(Option *opt, ChessProgramState *cps)
12732 // [HGM] options: process the string that defines an engine option, and determine
12733 // name, type, default value, and allowed value range
12734 {
12735         char *p, *q, buf[MSG_SIZ];
12736         int n, min = (-1)<<31, max = 1<<31, def;
12737
12738         if(p = strstr(opt->name, " -spin ")) {
12739             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12740             if(max < min) max = min; // enforce consistency
12741             if(def < min) def = min;
12742             if(def > max) def = max;
12743             opt->value = def;
12744             opt->min = min;
12745             opt->max = max;
12746             opt->type = Spin;
12747         } else if((p = strstr(opt->name, " -slider "))) {
12748             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12749             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12750             if(max < min) max = min; // enforce consistency
12751             if(def < min) def = min;
12752             if(def > max) def = max;
12753             opt->value = def;
12754             opt->min = min;
12755             opt->max = max;
12756             opt->type = Spin; // Slider;
12757         } else if((p = strstr(opt->name, " -string "))) {
12758             opt->textValue = p+9;
12759             opt->type = TextBox;
12760         } else if((p = strstr(opt->name, " -file "))) {
12761             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12762             opt->textValue = p+7;
12763             opt->type = TextBox; // FileName;
12764         } else if((p = strstr(opt->name, " -path "))) {
12765             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12766             opt->textValue = p+7;
12767             opt->type = TextBox; // PathName;
12768         } else if(p = strstr(opt->name, " -check ")) {
12769             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12770             opt->value = (def != 0);
12771             opt->type = CheckBox;
12772         } else if(p = strstr(opt->name, " -combo ")) {
12773             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12774             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12775             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12776             opt->value = n = 0;
12777             while(q = StrStr(q, " /// ")) {
12778                 n++; *q = 0;    // count choices, and null-terminate each of them
12779                 q += 5;
12780                 if(*q == '*') { // remember default, which is marked with * prefix
12781                     q++;
12782                     opt->value = n;
12783                 }
12784                 cps->comboList[cps->comboCnt++] = q;
12785             }
12786             cps->comboList[cps->comboCnt++] = NULL;
12787             opt->max = n + 1;
12788             opt->type = ComboBox;
12789         } else if(p = strstr(opt->name, " -button")) {
12790             opt->type = Button;
12791         } else if(p = strstr(opt->name, " -save")) {
12792             opt->type = SaveButton;
12793         } else return FALSE;
12794         *p = 0; // terminate option name
12795         // now look if the command-line options define a setting for this engine option.
12796         if(cps->optionSettings && cps->optionSettings[0])
12797             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12798         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12799                 sprintf(buf, "option %s", p);
12800                 if(p = strstr(buf, ",")) *p = 0;
12801                 strcat(buf, "\n");
12802                 SendToProgram(buf, cps);
12803         }
12804         return TRUE;
12805 }
12806
12807 void
12808 FeatureDone(cps, val)
12809      ChessProgramState* cps;
12810      int val;
12811 {
12812   DelayedEventCallback cb = GetDelayedEvent();
12813   if ((cb == InitBackEnd3 && cps == &first) ||
12814       (cb == TwoMachinesEventIfReady && cps == &second)) {
12815     CancelDelayedEvent();
12816     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12817   }
12818   cps->initDone = val;
12819 }
12820
12821 /* Parse feature command from engine */
12822 void
12823 ParseFeatures(args, cps)
12824      char* args;
12825      ChessProgramState *cps;  
12826 {
12827   char *p = args;
12828   char *q;
12829   int val;
12830   char buf[MSG_SIZ];
12831
12832   for (;;) {
12833     while (*p == ' ') p++;
12834     if (*p == NULLCHAR) return;
12835
12836     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12837     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12838     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12839     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12840     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12841     if (BoolFeature(&p, "reuse", &val, cps)) {
12842       /* Engine can disable reuse, but can't enable it if user said no */
12843       if (!val) cps->reuse = FALSE;
12844       continue;
12845     }
12846     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12847     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12848       if (gameMode == TwoMachinesPlay) {
12849         DisplayTwoMachinesTitle();
12850       } else {
12851         DisplayTitle("");
12852       }
12853       continue;
12854     }
12855     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12856     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12857     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12858     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12859     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12860     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12861     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12862     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12863     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12864     if (IntFeature(&p, "done", &val, cps)) {
12865       FeatureDone(cps, val);
12866       continue;
12867     }
12868     /* Added by Tord: */
12869     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12870     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12871     /* End of additions by Tord */
12872
12873     /* [HGM] added features: */
12874     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12875     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12876     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12877     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12878     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12879     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12880     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12881         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12882             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12883             SendToProgram(buf, cps);
12884             continue;
12885         }
12886         if(cps->nrOptions >= MAX_OPTIONS) {
12887             cps->nrOptions--;
12888             sprintf(buf, "%s engine has too many options\n", cps->which);
12889             DisplayError(buf, 0);
12890         }
12891         continue;
12892     }
12893     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12894     /* End of additions by HGM */
12895
12896     /* unknown feature: complain and skip */
12897     q = p;
12898     while (*q && *q != '=') q++;
12899     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12900     SendToProgram(buf, cps);
12901     p = q;
12902     if (*p == '=') {
12903       p++;
12904       if (*p == '\"') {
12905         p++;
12906         while (*p && *p != '\"') p++;
12907         if (*p == '\"') p++;
12908       } else {
12909         while (*p && *p != ' ') p++;
12910       }
12911     }
12912   }
12913
12914 }
12915
12916 void
12917 PeriodicUpdatesEvent(newState)
12918      int newState;
12919 {
12920     if (newState == appData.periodicUpdates)
12921       return;
12922
12923     appData.periodicUpdates=newState;
12924
12925     /* Display type changes, so update it now */
12926 //    DisplayAnalysis();
12927
12928     /* Get the ball rolling again... */
12929     if (newState) {
12930         AnalysisPeriodicEvent(1);
12931         StartAnalysisClock();
12932     }
12933 }
12934
12935 void
12936 PonderNextMoveEvent(newState)
12937      int newState;
12938 {
12939     if (newState == appData.ponderNextMove) return;
12940     if (gameMode == EditPosition) EditPositionDone(TRUE);
12941     if (newState) {
12942         SendToProgram("hard\n", &first);
12943         if (gameMode == TwoMachinesPlay) {
12944             SendToProgram("hard\n", &second);
12945         }
12946     } else {
12947         SendToProgram("easy\n", &first);
12948         thinkOutput[0] = NULLCHAR;
12949         if (gameMode == TwoMachinesPlay) {
12950             SendToProgram("easy\n", &second);
12951         }
12952     }
12953     appData.ponderNextMove = newState;
12954 }
12955
12956 void
12957 NewSettingEvent(option, command, value)
12958      char *command;
12959      int option, value;
12960 {
12961     char buf[MSG_SIZ];
12962
12963     if (gameMode == EditPosition) EditPositionDone(TRUE);
12964     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12965     SendToProgram(buf, &first);
12966     if (gameMode == TwoMachinesPlay) {
12967         SendToProgram(buf, &second);
12968     }
12969 }
12970
12971 void
12972 ShowThinkingEvent()
12973 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12974 {
12975     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12976     int newState = appData.showThinking
12977         // [HGM] thinking: other features now need thinking output as well
12978         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12979     
12980     if (oldState == newState) return;
12981     oldState = newState;
12982     if (gameMode == EditPosition) EditPositionDone(TRUE);
12983     if (oldState) {
12984         SendToProgram("post\n", &first);
12985         if (gameMode == TwoMachinesPlay) {
12986             SendToProgram("post\n", &second);
12987         }
12988     } else {
12989         SendToProgram("nopost\n", &first);
12990         thinkOutput[0] = NULLCHAR;
12991         if (gameMode == TwoMachinesPlay) {
12992             SendToProgram("nopost\n", &second);
12993         }
12994     }
12995 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12996 }
12997
12998 void
12999 AskQuestionEvent(title, question, replyPrefix, which)
13000      char *title; char *question; char *replyPrefix; char *which;
13001 {
13002   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13003   if (pr == NoProc) return;
13004   AskQuestion(title, question, replyPrefix, pr);
13005 }
13006
13007 void
13008 DisplayMove(moveNumber)
13009      int moveNumber;
13010 {
13011     char message[MSG_SIZ];
13012     char res[MSG_SIZ];
13013     char cpThinkOutput[MSG_SIZ];
13014
13015     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13016     
13017     if (moveNumber == forwardMostMove - 1 || 
13018         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13019
13020         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13021
13022         if (strchr(cpThinkOutput, '\n')) {
13023             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13024         }
13025     } else {
13026         *cpThinkOutput = NULLCHAR;
13027     }
13028
13029     /* [AS] Hide thinking from human user */
13030     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13031         *cpThinkOutput = NULLCHAR;
13032         if( thinkOutput[0] != NULLCHAR ) {
13033             int i;
13034
13035             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13036                 cpThinkOutput[i] = '.';
13037             }
13038             cpThinkOutput[i] = NULLCHAR;
13039             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13040         }
13041     }
13042
13043     if (moveNumber == forwardMostMove - 1 &&
13044         gameInfo.resultDetails != NULL) {
13045         if (gameInfo.resultDetails[0] == NULLCHAR) {
13046             sprintf(res, " %s", PGNResult(gameInfo.result));
13047         } else {
13048             sprintf(res, " {%s} %s",
13049                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13050         }
13051     } else {
13052         res[0] = NULLCHAR;
13053     }
13054
13055     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13056         DisplayMessage(res, cpThinkOutput);
13057     } else {
13058         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13059                 WhiteOnMove(moveNumber) ? " " : ".. ",
13060                 parseList[moveNumber], res);
13061         DisplayMessage(message, cpThinkOutput);
13062     }
13063 }
13064
13065 void
13066 DisplayComment(moveNumber, text)
13067      int moveNumber;
13068      char *text;
13069 {
13070     char title[MSG_SIZ];
13071     char buf[8000]; // comment can be long!
13072     int score, depth;
13073     
13074     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13075       strcpy(title, "Comment");
13076     } else {
13077       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13078               WhiteOnMove(moveNumber) ? " " : ".. ",
13079               parseList[moveNumber]);
13080     }
13081     // [HGM] PV info: display PV info together with (or as) comment
13082     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13083       if(text == NULL) text = "";                                           
13084       score = pvInfoList[moveNumber].score;
13085       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13086               depth, (pvInfoList[moveNumber].time+50)/100, text);
13087       text = buf;
13088     }
13089     if (text != NULL && (appData.autoDisplayComment || commentUp))
13090         CommentPopUp(title, text);
13091 }
13092
13093 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13094  * might be busy thinking or pondering.  It can be omitted if your
13095  * gnuchess is configured to stop thinking immediately on any user
13096  * input.  However, that gnuchess feature depends on the FIONREAD
13097  * ioctl, which does not work properly on some flavors of Unix.
13098  */
13099 void
13100 Attention(cps)
13101      ChessProgramState *cps;
13102 {
13103 #if ATTENTION
13104     if (!cps->useSigint) return;
13105     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13106     switch (gameMode) {
13107       case MachinePlaysWhite:
13108       case MachinePlaysBlack:
13109       case TwoMachinesPlay:
13110       case IcsPlayingWhite:
13111       case IcsPlayingBlack:
13112       case AnalyzeMode:
13113       case AnalyzeFile:
13114         /* Skip if we know it isn't thinking */
13115         if (!cps->maybeThinking) return;
13116         if (appData.debugMode)
13117           fprintf(debugFP, "Interrupting %s\n", cps->which);
13118         InterruptChildProcess(cps->pr);
13119         cps->maybeThinking = FALSE;
13120         break;
13121       default:
13122         break;
13123     }
13124 #endif /*ATTENTION*/
13125 }
13126
13127 int
13128 CheckFlags()
13129 {
13130     if (whiteTimeRemaining <= 0) {
13131         if (!whiteFlag) {
13132             whiteFlag = TRUE;
13133             if (appData.icsActive) {
13134                 if (appData.autoCallFlag &&
13135                     gameMode == IcsPlayingBlack && !blackFlag) {
13136                   SendToICS(ics_prefix);
13137                   SendToICS("flag\n");
13138                 }
13139             } else {
13140                 if (blackFlag) {
13141                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13142                 } else {
13143                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13144                     if (appData.autoCallFlag) {
13145                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13146                         return TRUE;
13147                     }
13148                 }
13149             }
13150         }
13151     }
13152     if (blackTimeRemaining <= 0) {
13153         if (!blackFlag) {
13154             blackFlag = TRUE;
13155             if (appData.icsActive) {
13156                 if (appData.autoCallFlag &&
13157                     gameMode == IcsPlayingWhite && !whiteFlag) {
13158                   SendToICS(ics_prefix);
13159                   SendToICS("flag\n");
13160                 }
13161             } else {
13162                 if (whiteFlag) {
13163                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13164                 } else {
13165                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13166                     if (appData.autoCallFlag) {
13167                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13168                         return TRUE;
13169                     }
13170                 }
13171             }
13172         }
13173     }
13174     return FALSE;
13175 }
13176
13177 void
13178 CheckTimeControl()
13179 {
13180     if (!appData.clockMode || appData.icsActive ||
13181         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13182
13183     /*
13184      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13185      */
13186     if ( !WhiteOnMove(forwardMostMove) )
13187         /* White made time control */
13188         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13189         /* [HGM] time odds: correct new time quota for time odds! */
13190                                             / WhitePlayer()->timeOdds;
13191       else
13192         /* Black made time control */
13193         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13194                                             / WhitePlayer()->other->timeOdds;
13195 }
13196
13197 void
13198 DisplayBothClocks()
13199 {
13200     int wom = gameMode == EditPosition ?
13201       !blackPlaysFirst : WhiteOnMove(currentMove);
13202     DisplayWhiteClock(whiteTimeRemaining, wom);
13203     DisplayBlackClock(blackTimeRemaining, !wom);
13204 }
13205
13206
13207 /* Timekeeping seems to be a portability nightmare.  I think everyone
13208    has ftime(), but I'm really not sure, so I'm including some ifdefs
13209    to use other calls if you don't.  Clocks will be less accurate if
13210    you have neither ftime nor gettimeofday.
13211 */
13212
13213 /* VS 2008 requires the #include outside of the function */
13214 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13215 #include <sys/timeb.h>
13216 #endif
13217
13218 /* Get the current time as a TimeMark */
13219 void
13220 GetTimeMark(tm)
13221      TimeMark *tm;
13222 {
13223 #if HAVE_GETTIMEOFDAY
13224
13225     struct timeval timeVal;
13226     struct timezone timeZone;
13227
13228     gettimeofday(&timeVal, &timeZone);
13229     tm->sec = (long) timeVal.tv_sec; 
13230     tm->ms = (int) (timeVal.tv_usec / 1000L);
13231
13232 #else /*!HAVE_GETTIMEOFDAY*/
13233 #if HAVE_FTIME
13234
13235 // include <sys/timeb.h> / moved to just above start of function
13236     struct timeb timeB;
13237
13238     ftime(&timeB);
13239     tm->sec = (long) timeB.time;
13240     tm->ms = (int) timeB.millitm;
13241
13242 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13243     tm->sec = (long) time(NULL);
13244     tm->ms = 0;
13245 #endif
13246 #endif
13247 }
13248
13249 /* Return the difference in milliseconds between two
13250    time marks.  We assume the difference will fit in a long!
13251 */
13252 long
13253 SubtractTimeMarks(tm2, tm1)
13254      TimeMark *tm2, *tm1;
13255 {
13256     return 1000L*(tm2->sec - tm1->sec) +
13257            (long) (tm2->ms - tm1->ms);
13258 }
13259
13260
13261 /*
13262  * Code to manage the game clocks.
13263  *
13264  * In tournament play, black starts the clock and then white makes a move.
13265  * We give the human user a slight advantage if he is playing white---the
13266  * clocks don't run until he makes his first move, so it takes zero time.
13267  * Also, we don't account for network lag, so we could get out of sync
13268  * with GNU Chess's clock -- but then, referees are always right.  
13269  */
13270
13271 static TimeMark tickStartTM;
13272 static long intendedTickLength;
13273
13274 long
13275 NextTickLength(timeRemaining)
13276      long timeRemaining;
13277 {
13278     long nominalTickLength, nextTickLength;
13279
13280     if (timeRemaining > 0L && timeRemaining <= 10000L)
13281       nominalTickLength = 100L;
13282     else
13283       nominalTickLength = 1000L;
13284     nextTickLength = timeRemaining % nominalTickLength;
13285     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13286
13287     return nextTickLength;
13288 }
13289
13290 /* Adjust clock one minute up or down */
13291 void
13292 AdjustClock(Boolean which, int dir)
13293 {
13294     if(which) blackTimeRemaining += 60000*dir;
13295     else      whiteTimeRemaining += 60000*dir;
13296     DisplayBothClocks();
13297 }
13298
13299 /* Stop clocks and reset to a fresh time control */
13300 void
13301 ResetClocks() 
13302 {
13303     (void) StopClockTimer();
13304     if (appData.icsActive) {
13305         whiteTimeRemaining = blackTimeRemaining = 0;
13306     } else { /* [HGM] correct new time quote for time odds */
13307         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13308         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13309     }
13310     if (whiteFlag || blackFlag) {
13311         DisplayTitle("");
13312         whiteFlag = blackFlag = FALSE;
13313     }
13314     DisplayBothClocks();
13315 }
13316
13317 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13318
13319 /* Decrement running clock by amount of time that has passed */
13320 void
13321 DecrementClocks()
13322 {
13323     long timeRemaining;
13324     long lastTickLength, fudge;
13325     TimeMark now;
13326
13327     if (!appData.clockMode) return;
13328     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13329         
13330     GetTimeMark(&now);
13331
13332     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13333
13334     /* Fudge if we woke up a little too soon */
13335     fudge = intendedTickLength - lastTickLength;
13336     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13337
13338     if (WhiteOnMove(forwardMostMove)) {
13339         if(whiteNPS >= 0) lastTickLength = 0;
13340         timeRemaining = whiteTimeRemaining -= lastTickLength;
13341         DisplayWhiteClock(whiteTimeRemaining - fudge,
13342                           WhiteOnMove(currentMove));
13343     } else {
13344         if(blackNPS >= 0) lastTickLength = 0;
13345         timeRemaining = blackTimeRemaining -= lastTickLength;
13346         DisplayBlackClock(blackTimeRemaining - fudge,
13347                           !WhiteOnMove(currentMove));
13348     }
13349
13350     if (CheckFlags()) return;
13351         
13352     tickStartTM = now;
13353     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13354     StartClockTimer(intendedTickLength);
13355
13356     /* if the time remaining has fallen below the alarm threshold, sound the
13357      * alarm. if the alarm has sounded and (due to a takeback or time control
13358      * with increment) the time remaining has increased to a level above the
13359      * threshold, reset the alarm so it can sound again. 
13360      */
13361     
13362     if (appData.icsActive && appData.icsAlarm) {
13363
13364         /* make sure we are dealing with the user's clock */
13365         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13366                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13367            )) return;
13368
13369         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13370             alarmSounded = FALSE;
13371         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13372             PlayAlarmSound();
13373             alarmSounded = TRUE;
13374         }
13375     }
13376 }
13377
13378
13379 /* A player has just moved, so stop the previously running
13380    clock and (if in clock mode) start the other one.
13381    We redisplay both clocks in case we're in ICS mode, because
13382    ICS gives us an update to both clocks after every move.
13383    Note that this routine is called *after* forwardMostMove
13384    is updated, so the last fractional tick must be subtracted
13385    from the color that is *not* on move now.
13386 */
13387 void
13388 SwitchClocks(int newMoveNr)
13389 {
13390     long lastTickLength;
13391     TimeMark now;
13392     int flagged = FALSE;
13393
13394     GetTimeMark(&now);
13395
13396     if (StopClockTimer() && appData.clockMode) {
13397         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13398         if (!WhiteOnMove(forwardMostMove)) {
13399             if(blackNPS >= 0) lastTickLength = 0;
13400             blackTimeRemaining -= lastTickLength;
13401            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13402 //         if(pvInfoList[forwardMostMove-1].time == -1)
13403                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13404                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13405         } else {
13406            if(whiteNPS >= 0) lastTickLength = 0;
13407            whiteTimeRemaining -= lastTickLength;
13408            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13409 //         if(pvInfoList[forwardMostMove-1].time == -1)
13410                  pvInfoList[forwardMostMove-1].time = 
13411                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13412         }
13413         flagged = CheckFlags();
13414     }
13415     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
13416     CheckTimeControl();
13417
13418     if (flagged || !appData.clockMode) return;
13419
13420     switch (gameMode) {
13421       case MachinePlaysBlack:
13422       case MachinePlaysWhite:
13423       case BeginningOfGame:
13424         if (pausing) return;
13425         break;
13426
13427       case EditGame:
13428       case PlayFromGameFile:
13429       case IcsExamining:
13430         return;
13431
13432       default:
13433         break;
13434     }
13435
13436     tickStartTM = now;
13437     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13438       whiteTimeRemaining : blackTimeRemaining);
13439     StartClockTimer(intendedTickLength);
13440 }
13441         
13442
13443 /* Stop both clocks */
13444 void
13445 StopClocks()
13446 {       
13447     long lastTickLength;
13448     TimeMark now;
13449
13450     if (!StopClockTimer()) return;
13451     if (!appData.clockMode) return;
13452
13453     GetTimeMark(&now);
13454
13455     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13456     if (WhiteOnMove(forwardMostMove)) {
13457         if(whiteNPS >= 0) lastTickLength = 0;
13458         whiteTimeRemaining -= lastTickLength;
13459         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13460     } else {
13461         if(blackNPS >= 0) lastTickLength = 0;
13462         blackTimeRemaining -= lastTickLength;
13463         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13464     }
13465     CheckFlags();
13466 }
13467         
13468 /* Start clock of player on move.  Time may have been reset, so
13469    if clock is already running, stop and restart it. */
13470 void
13471 StartClocks()
13472 {
13473     (void) StopClockTimer(); /* in case it was running already */
13474     DisplayBothClocks();
13475     if (CheckFlags()) return;
13476
13477     if (!appData.clockMode) return;
13478     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13479
13480     GetTimeMark(&tickStartTM);
13481     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13482       whiteTimeRemaining : blackTimeRemaining);
13483
13484    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13485     whiteNPS = blackNPS = -1; 
13486     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13487        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13488         whiteNPS = first.nps;
13489     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13490        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13491         blackNPS = first.nps;
13492     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13493         whiteNPS = second.nps;
13494     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13495         blackNPS = second.nps;
13496     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13497
13498     StartClockTimer(intendedTickLength);
13499 }
13500
13501 char *
13502 TimeString(ms)
13503      long ms;
13504 {
13505     long second, minute, hour, day;
13506     char *sign = "";
13507     static char buf[32];
13508     
13509     if (ms > 0 && ms <= 9900) {
13510       /* convert milliseconds to tenths, rounding up */
13511       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13512
13513       sprintf(buf, " %03.1f ", tenths/10.0);
13514       return buf;
13515     }
13516
13517     /* convert milliseconds to seconds, rounding up */
13518     /* use floating point to avoid strangeness of integer division
13519        with negative dividends on many machines */
13520     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13521
13522     if (second < 0) {
13523         sign = "-";
13524         second = -second;
13525     }
13526     
13527     day = second / (60 * 60 * 24);
13528     second = second % (60 * 60 * 24);
13529     hour = second / (60 * 60);
13530     second = second % (60 * 60);
13531     minute = second / 60;
13532     second = second % 60;
13533     
13534     if (day > 0)
13535       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13536               sign, day, hour, minute, second);
13537     else if (hour > 0)
13538       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13539     else
13540       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13541     
13542     return buf;
13543 }
13544
13545
13546 /*
13547  * This is necessary because some C libraries aren't ANSI C compliant yet.
13548  */
13549 char *
13550 StrStr(string, match)
13551      char *string, *match;
13552 {
13553     int i, length;
13554     
13555     length = strlen(match);
13556     
13557     for (i = strlen(string) - length; i >= 0; i--, string++)
13558       if (!strncmp(match, string, length))
13559         return string;
13560     
13561     return NULL;
13562 }
13563
13564 char *
13565 StrCaseStr(string, match)
13566      char *string, *match;
13567 {
13568     int i, j, length;
13569     
13570     length = strlen(match);
13571     
13572     for (i = strlen(string) - length; i >= 0; i--, string++) {
13573         for (j = 0; j < length; j++) {
13574             if (ToLower(match[j]) != ToLower(string[j]))
13575               break;
13576         }
13577         if (j == length) return string;
13578     }
13579
13580     return NULL;
13581 }
13582
13583 #ifndef _amigados
13584 int
13585 StrCaseCmp(s1, s2)
13586      char *s1, *s2;
13587 {
13588     char c1, c2;
13589     
13590     for (;;) {
13591         c1 = ToLower(*s1++);
13592         c2 = ToLower(*s2++);
13593         if (c1 > c2) return 1;
13594         if (c1 < c2) return -1;
13595         if (c1 == NULLCHAR) return 0;
13596     }
13597 }
13598
13599
13600 int
13601 ToLower(c)
13602      int c;
13603 {
13604     return isupper(c) ? tolower(c) : c;
13605 }
13606
13607
13608 int
13609 ToUpper(c)
13610      int c;
13611 {
13612     return islower(c) ? toupper(c) : c;
13613 }
13614 #endif /* !_amigados    */
13615
13616 char *
13617 StrSave(s)
13618      char *s;
13619 {
13620     char *ret;
13621
13622     if ((ret = (char *) malloc(strlen(s) + 1))) {
13623         strcpy(ret, s);
13624     }
13625     return ret;
13626 }
13627
13628 char *
13629 StrSavePtr(s, savePtr)
13630      char *s, **savePtr;
13631 {
13632     if (*savePtr) {
13633         free(*savePtr);
13634     }
13635     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13636         strcpy(*savePtr, s);
13637     }
13638     return(*savePtr);
13639 }
13640
13641 char *
13642 PGNDate()
13643 {
13644     time_t clock;
13645     struct tm *tm;
13646     char buf[MSG_SIZ];
13647
13648     clock = time((time_t *)NULL);
13649     tm = localtime(&clock);
13650     sprintf(buf, "%04d.%02d.%02d",
13651             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13652     return StrSave(buf);
13653 }
13654
13655
13656 char *
13657 PositionToFEN(move, overrideCastling)
13658      int move;
13659      char *overrideCastling;
13660 {
13661     int i, j, fromX, fromY, toX, toY;
13662     int whiteToPlay;
13663     char buf[128];
13664     char *p, *q;
13665     int emptycount;
13666     ChessSquare piece;
13667
13668     whiteToPlay = (gameMode == EditPosition) ?
13669       !blackPlaysFirst : (move % 2 == 0);
13670     p = buf;
13671
13672     /* Piece placement data */
13673     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13674         emptycount = 0;
13675         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13676             if (boards[move][i][j] == EmptySquare) {
13677                 emptycount++;
13678             } else { ChessSquare piece = boards[move][i][j];
13679                 if (emptycount > 0) {
13680                     if(emptycount<10) /* [HGM] can be >= 10 */
13681                         *p++ = '0' + emptycount;
13682                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13683                     emptycount = 0;
13684                 }
13685                 if(PieceToChar(piece) == '+') {
13686                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13687                     *p++ = '+';
13688                     piece = (ChessSquare)(DEMOTED piece);
13689                 } 
13690                 *p++ = PieceToChar(piece);
13691                 if(p[-1] == '~') {
13692                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13693                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13694                     *p++ = '~';
13695                 }
13696             }
13697         }
13698         if (emptycount > 0) {
13699             if(emptycount<10) /* [HGM] can be >= 10 */
13700                 *p++ = '0' + emptycount;
13701             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13702             emptycount = 0;
13703         }
13704         *p++ = '/';
13705     }
13706     *(p - 1) = ' ';
13707
13708     /* [HGM] print Crazyhouse or Shogi holdings */
13709     if( gameInfo.holdingsWidth ) {
13710         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13711         q = p;
13712         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13713             piece = boards[move][i][BOARD_WIDTH-1];
13714             if( piece != EmptySquare )
13715               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13716                   *p++ = PieceToChar(piece);
13717         }
13718         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13719             piece = boards[move][BOARD_HEIGHT-i-1][0];
13720             if( piece != EmptySquare )
13721               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13722                   *p++ = PieceToChar(piece);
13723         }
13724
13725         if( q == p ) *p++ = '-';
13726         *p++ = ']';
13727         *p++ = ' ';
13728     }
13729
13730     /* Active color */
13731     *p++ = whiteToPlay ? 'w' : 'b';
13732     *p++ = ' ';
13733
13734   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13735     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13736   } else {
13737   if(nrCastlingRights) {
13738      q = p;
13739      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13740        /* [HGM] write directly from rights */
13741            if(castlingRights[move][2] >= 0 &&
13742               castlingRights[move][0] >= 0   )
13743                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13744            if(castlingRights[move][2] >= 0 &&
13745               castlingRights[move][1] >= 0   )
13746                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13747            if(castlingRights[move][5] >= 0 &&
13748               castlingRights[move][3] >= 0   )
13749                 *p++ = castlingRights[move][3] + AAA;
13750            if(castlingRights[move][5] >= 0 &&
13751               castlingRights[move][4] >= 0   )
13752                 *p++ = castlingRights[move][4] + AAA;
13753      } else {
13754
13755         /* [HGM] write true castling rights */
13756         if( nrCastlingRights == 6 ) {
13757             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13758                castlingRights[move][2] >= 0  ) *p++ = 'K';
13759             if(castlingRights[move][1] == BOARD_LEFT &&
13760                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13761             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13762                castlingRights[move][5] >= 0  ) *p++ = 'k';
13763             if(castlingRights[move][4] == BOARD_LEFT &&
13764                castlingRights[move][5] >= 0  ) *p++ = 'q';
13765         }
13766      }
13767      if (q == p) *p++ = '-'; /* No castling rights */
13768      *p++ = ' ';
13769   }
13770
13771   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13772      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
13773     /* En passant target square */
13774     if (move > backwardMostMove) {
13775         fromX = moveList[move - 1][0] - AAA;
13776         fromY = moveList[move - 1][1] - ONE;
13777         toX = moveList[move - 1][2] - AAA;
13778         toY = moveList[move - 1][3] - ONE;
13779         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13780             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13781             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13782             fromX == toX) {
13783             /* 2-square pawn move just happened */
13784             *p++ = toX + AAA;
13785             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13786         } else {
13787             *p++ = '-';
13788         }
13789     } else if(move == backwardMostMove) {
13790         // [HGM] perhaps we should always do it like this, and forget the above?
13791         if(epStatus[move] >= 0) {
13792             *p++ = epStatus[move] + AAA;
13793             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13794         } else {
13795             *p++ = '-';
13796         }
13797     } else {
13798         *p++ = '-';
13799     }
13800     *p++ = ' ';
13801   }
13802   }
13803
13804     /* [HGM] find reversible plies */
13805     {   int i = 0, j=move;
13806
13807         if (appData.debugMode) { int k;
13808             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13809             for(k=backwardMostMove; k<=forwardMostMove; k++)
13810                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13811
13812         }
13813
13814         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13815         if( j == backwardMostMove ) i += initialRulePlies;
13816         sprintf(p, "%d ", i);
13817         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13818     }
13819     /* Fullmove number */
13820     sprintf(p, "%d", (move / 2) + 1);
13821     
13822     return StrSave(buf);
13823 }
13824
13825 Boolean
13826 ParseFEN(board, blackPlaysFirst, fen)
13827     Board board;
13828      int *blackPlaysFirst;
13829      char *fen;
13830 {
13831     int i, j;
13832     char *p;
13833     int emptycount;
13834     ChessSquare piece;
13835
13836     p = fen;
13837
13838     /* [HGM] by default clear Crazyhouse holdings, if present */
13839     if(gameInfo.holdingsWidth) {
13840        for(i=0; i<BOARD_HEIGHT; i++) {
13841            board[i][0]             = EmptySquare; /* black holdings */
13842            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13843            board[i][1]             = (ChessSquare) 0; /* black counts */
13844            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13845        }
13846     }
13847
13848     /* Piece placement data */
13849     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13850         j = 0;
13851         for (;;) {
13852             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13853                 if (*p == '/') p++;
13854                 emptycount = gameInfo.boardWidth - j;
13855                 while (emptycount--)
13856                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13857                 break;
13858 #if(BOARD_SIZE >= 10)
13859             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13860                 p++; emptycount=10;
13861                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13862                 while (emptycount--)
13863                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13864 #endif
13865             } else if (isdigit(*p)) {
13866                 emptycount = *p++ - '0';
13867                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13868                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13869                 while (emptycount--)
13870                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13871             } else if (*p == '+' || isalpha(*p)) {
13872                 if (j >= gameInfo.boardWidth) return FALSE;
13873                 if(*p=='+') {
13874                     piece = CharToPiece(*++p);
13875                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13876                     piece = (ChessSquare) (PROMOTED piece ); p++;
13877                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13878                 } else piece = CharToPiece(*p++);
13879
13880                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13881                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13882                     piece = (ChessSquare) (PROMOTED piece);
13883                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13884                     p++;
13885                 }
13886                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13887             } else {
13888                 return FALSE;
13889             }
13890         }
13891     }
13892     while (*p == '/' || *p == ' ') p++;
13893
13894     /* [HGM] look for Crazyhouse holdings here */
13895     while(*p==' ') p++;
13896     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13897         if(*p == '[') p++;
13898         if(*p == '-' ) *p++; /* empty holdings */ else {
13899             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13900             /* if we would allow FEN reading to set board size, we would   */
13901             /* have to add holdings and shift the board read so far here   */
13902             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13903                 *p++;
13904                 if((int) piece >= (int) BlackPawn ) {
13905                     i = (int)piece - (int)BlackPawn;
13906                     i = PieceToNumber((ChessSquare)i);
13907                     if( i >= gameInfo.holdingsSize ) return FALSE;
13908                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13909                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13910                 } else {
13911                     i = (int)piece - (int)WhitePawn;
13912                     i = PieceToNumber((ChessSquare)i);
13913                     if( i >= gameInfo.holdingsSize ) return FALSE;
13914                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13915                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13916                 }
13917             }
13918         }
13919         if(*p == ']') *p++;
13920     }
13921
13922     while(*p == ' ') p++;
13923
13924     /* Active color */
13925     switch (*p++) {
13926       case 'w':
13927         *blackPlaysFirst = FALSE;
13928         break;
13929       case 'b': 
13930         *blackPlaysFirst = TRUE;
13931         break;
13932       default:
13933         return FALSE;
13934     }
13935
13936     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13937     /* return the extra info in global variiables             */
13938
13939     /* set defaults in case FEN is incomplete */
13940     FENepStatus = EP_UNKNOWN;
13941     for(i=0; i<nrCastlingRights; i++ ) {
13942         FENcastlingRights[i] =
13943             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13944     }   /* assume possible unless obviously impossible */
13945     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13946     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13947     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13948                            && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13949     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13950     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13951     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13952                            && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13953     FENrulePlies = 0;
13954
13955     while(*p==' ') p++;
13956     if(nrCastlingRights) {
13957       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13958           /* castling indicator present, so default becomes no castlings */
13959           for(i=0; i<nrCastlingRights; i++ ) {
13960                  FENcastlingRights[i] = -1;
13961           }
13962       }
13963       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13964              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13965              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13966              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13967         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13968
13969         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13970             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13971             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13972         }
13973         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13974             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13975         if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13976                            && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13977         if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13978                            && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13979         switch(c) {
13980           case'K':
13981               for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13982               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13983               FENcastlingRights[2] = whiteKingFile;
13984               break;
13985           case'Q':
13986               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13987               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13988               FENcastlingRights[2] = whiteKingFile;
13989               break;
13990           case'k':
13991               for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
13992               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13993               FENcastlingRights[5] = blackKingFile;
13994               break;
13995           case'q':
13996               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13997               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13998               FENcastlingRights[5] = blackKingFile;
13999           case '-':
14000               break;
14001           default: /* FRC castlings */
14002               if(c >= 'a') { /* black rights */
14003                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14004                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14005                   if(i == BOARD_RGHT) break;
14006                   FENcastlingRights[5] = i;
14007                   c -= AAA;
14008                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14009                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14010                   if(c > i)
14011                       FENcastlingRights[3] = c;
14012                   else
14013                       FENcastlingRights[4] = c;
14014               } else { /* white rights */
14015                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14016                     if(board[0][i] == WhiteKing) break;
14017                   if(i == BOARD_RGHT) break;
14018                   FENcastlingRights[2] = i;
14019                   c -= AAA - 'a' + 'A';
14020                   if(board[0][c] >= WhiteKing) break;
14021                   if(c > i)
14022                       FENcastlingRights[0] = c;
14023                   else
14024                       FENcastlingRights[1] = c;
14025               }
14026         }
14027       }
14028       for(i=0; i<nrCastlingRights; i++)
14029         if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
14030     if (appData.debugMode) {
14031         fprintf(debugFP, "FEN castling rights:");
14032         for(i=0; i<nrCastlingRights; i++)
14033         fprintf(debugFP, " %d", FENcastlingRights[i]);
14034         fprintf(debugFP, "\n");
14035     }
14036
14037       while(*p==' ') p++;
14038     }
14039
14040     /* read e.p. field in games that know e.p. capture */
14041     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14042        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14043       if(*p=='-') {
14044         p++; FENepStatus = EP_NONE;
14045       } else {
14046          char c = *p++ - AAA;
14047
14048          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14049          if(*p >= '0' && *p <='9') *p++;
14050          FENepStatus = c;
14051       }
14052     }
14053
14054
14055     if(sscanf(p, "%d", &i) == 1) {
14056         FENrulePlies = i; /* 50-move ply counter */
14057         /* (The move number is still ignored)    */
14058     }
14059
14060     return TRUE;
14061 }
14062       
14063 void
14064 EditPositionPasteFEN(char *fen)
14065 {
14066   if (fen != NULL) {
14067     Board initial_position;
14068
14069     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14070       DisplayError(_("Bad FEN position in clipboard"), 0);
14071       return ;
14072     } else {
14073       int savedBlackPlaysFirst = blackPlaysFirst;
14074       EditPositionEvent();
14075       blackPlaysFirst = savedBlackPlaysFirst;
14076       CopyBoard(boards[0], initial_position);
14077           /* [HGM] copy FEN attributes as well */
14078           {   int i;
14079               initialRulePlies = FENrulePlies;
14080               epStatus[0] = FENepStatus;
14081               for( i=0; i<nrCastlingRights; i++ )
14082                   castlingRights[0][i] = FENcastlingRights[i];
14083           }
14084       EditPositionDone(FALSE);
14085       DisplayBothClocks();
14086       DrawPosition(FALSE, boards[currentMove]);
14087     }
14088   }
14089 }
14090
14091 static char cseq[12] = "\\   ";
14092
14093 Boolean set_cont_sequence(char *new_seq)
14094 {
14095     int len;
14096     Boolean ret;
14097
14098     // handle bad attempts to set the sequence
14099         if (!new_seq)
14100                 return 0; // acceptable error - no debug
14101
14102     len = strlen(new_seq);
14103     ret = (len > 0) && (len < sizeof(cseq));
14104     if (ret)
14105         strcpy(cseq, new_seq);
14106     else if (appData.debugMode)
14107         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14108     return ret;
14109 }
14110
14111 /*
14112     reformat a source message so words don't cross the width boundary.  internal
14113     newlines are not removed.  returns the wrapped size (no null character unless
14114     included in source message).  If dest is NULL, only calculate the size required
14115     for the dest buffer.  lp argument indicats line position upon entry, and it's
14116     passed back upon exit.
14117 */
14118 int wrap(char *dest, char *src, int count, int width, int *lp)
14119 {
14120     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14121
14122     cseq_len = strlen(cseq);
14123     old_line = line = *lp;
14124     ansi = len = clen = 0;
14125
14126     for (i=0; i < count; i++)
14127     {
14128         if (src[i] == '\033')
14129             ansi = 1;
14130
14131         // if we hit the width, back up
14132         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14133         {
14134             // store i & len in case the word is too long
14135             old_i = i, old_len = len;
14136
14137             // find the end of the last word
14138             while (i && src[i] != ' ' && src[i] != '\n')
14139             {
14140                 i--;
14141                 len--;
14142             }
14143
14144             // word too long?  restore i & len before splitting it
14145             if ((old_i-i+clen) >= width)
14146             {
14147                 i = old_i;
14148                 len = old_len;
14149             }
14150
14151             // extra space?
14152             if (i && src[i-1] == ' ')
14153                 len--;
14154
14155             if (src[i] != ' ' && src[i] != '\n')
14156             {
14157                 i--;
14158                 if (len)
14159                     len--;
14160             }
14161
14162             // now append the newline and continuation sequence
14163             if (dest)
14164                 dest[len] = '\n';
14165             len++;
14166             if (dest)
14167                 strncpy(dest+len, cseq, cseq_len);
14168             len += cseq_len;
14169             line = cseq_len;
14170             clen = cseq_len;
14171             continue;
14172         }
14173
14174         if (dest)
14175             dest[len] = src[i];
14176         len++;
14177         if (!ansi)
14178             line++;
14179         if (src[i] == '\n')
14180             line = 0;
14181         if (src[i] == 'm')
14182             ansi = 0;
14183     }
14184     if (dest && appData.debugMode)
14185     {
14186         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14187             count, width, line, len, *lp);
14188         show_bytes(debugFP, src, count);
14189         fprintf(debugFP, "\ndest: ");
14190         show_bytes(debugFP, dest, len);
14191         fprintf(debugFP, "\n");
14192     }
14193     *lp = dest ? line : old_line;
14194
14195     return len;
14196 }