Maintainence to support all compilers.
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                   Board board, 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((void));
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 void DisplayAnalysis P((void));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void StartClocks P((void));
192 void SwitchClocks P((void));
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 int opponentKibitzes;
247 int lastSavedGame; /* [HGM] save: ID of game */
248 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
249 extern int chatCount;
250 int chattingPartner;
251
252 /* States for ics_getting_history */
253 #define H_FALSE 0
254 #define H_REQUESTED 1
255 #define H_GOT_REQ_HEADER 2
256 #define H_GOT_UNREQ_HEADER 3
257 #define H_GETTING_MOVES 4
258 #define H_GOT_UNWANTED_HEADER 5
259
260 /* whosays values for GameEnds */
261 #define GE_ICS 0
262 #define GE_ENGINE 1
263 #define GE_PLAYER 2
264 #define GE_FILE 3
265 #define GE_XBOARD 4
266 #define GE_ENGINE1 5
267 #define GE_ENGINE2 6
268
269 /* Maximum number of games in a cmail message */
270 #define CMAIL_MAX_GAMES 20
271
272 /* Different types of move when calling RegisterMove */
273 #define CMAIL_MOVE   0
274 #define CMAIL_RESIGN 1
275 #define CMAIL_DRAW   2
276 #define CMAIL_ACCEPT 3
277
278 /* Different types of result to remember for each game */
279 #define CMAIL_NOT_RESULT 0
280 #define CMAIL_OLD_RESULT 1
281 #define CMAIL_NEW_RESULT 2
282
283 /* Telnet protocol constants */
284 #define TN_WILL 0373
285 #define TN_WONT 0374
286 #define TN_DO   0375
287 #define TN_DONT 0376
288 #define TN_IAC  0377
289 #define TN_ECHO 0001
290 #define TN_SGA  0003
291 #define TN_PORT 23
292
293 /* [AS] */
294 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 {
296     assert( dst != NULL );
297     assert( src != NULL );
298     assert( count > 0 );
299
300     strncpy( dst, src, count );
301     dst[ count-1 ] = '\0';
302     return dst;
303 }
304
305 /* Some compiler can't cast u64 to double
306  * This function do the job for us:
307
308  * We use the highest bit for cast, this only
309  * works if the highest bit is not
310  * in use (This should not happen)
311  *
312  * We used this for all compiler
313  */
314 double
315 u64ToDouble(u64 value)
316 {
317   double r;
318   u64 tmp = value & u64Const(0x7fffffffffffffff);
319   r = (double)(s64)tmp;
320   if (value & u64Const(0x8000000000000000))
321        r +=  9.2233720368547758080e18; /* 2^63 */
322  return r;
323 }
324
325 /* Fake up flags for now, as we aren't keeping track of castling
326    availability yet. [HGM] Change of logic: the flag now only
327    indicates the type of castlings allowed by the rule of the game.
328    The actual rights themselves are maintained in the array
329    castlingRights, as part of the game history, and are not probed
330    by this function.
331  */
332 int
333 PosFlags(index)
334 {
335   int flags = F_ALL_CASTLE_OK;
336   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
337   switch (gameInfo.variant) {
338   case VariantSuicide:
339     flags &= ~F_ALL_CASTLE_OK;
340   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
341     flags |= F_IGNORE_CHECK;
342   case VariantLosers:
343     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
344     break;
345   case VariantAtomic:
346     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347     break;
348   case VariantKriegspiel:
349     flags |= F_KRIEGSPIEL_CAPTURE;
350     break;
351   case VariantCapaRandom: 
352   case VariantFischeRandom:
353     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
354   case VariantNoCastle:
355   case VariantShatranj:
356   case VariantCourier:
357     flags &= ~F_ALL_CASTLE_OK;
358     break;
359   default:
360     break;
361   }
362   return flags;
363 }
364
365 FILE *gameFileFP, *debugFP;
366
367 /* 
368     [AS] Note: sometimes, the sscanf() function is used to parse the input
369     into a fixed-size buffer. Because of this, we must be prepared to
370     receive strings as long as the size of the input buffer, which is currently
371     set to 4K for Windows and 8K for the rest.
372     So, we must either allocate sufficiently large buffers here, or
373     reduce the size of the input buffer in the input reading part.
374 */
375
376 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
377 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
378 char thinkOutput1[MSG_SIZ*10];
379
380 ChessProgramState first, second;
381
382 /* premove variables */
383 int premoveToX = 0;
384 int premoveToY = 0;
385 int premoveFromX = 0;
386 int premoveFromY = 0;
387 int premovePromoChar = 0;
388 int gotPremove = 0;
389 Boolean alarmSounded;
390 /* end premove variables */
391
392 char *ics_prefix = "$";
393 int ics_type = ICS_GENERIC;
394
395 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
396 int pauseExamForwardMostMove = 0;
397 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
398 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
399 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
400 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
401 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
402 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
403 int whiteFlag = FALSE, blackFlag = FALSE;
404 int userOfferedDraw = FALSE;
405 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
406 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
407 int cmailMoveType[CMAIL_MAX_GAMES];
408 long ics_clock_paused = 0;
409 ProcRef icsPR = NoProc, cmailPR = NoProc;
410 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
411 GameMode gameMode = BeginningOfGame;
412 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
413 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
414 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
415 int hiddenThinkOutputState = 0; /* [AS] */
416 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
417 int adjudicateLossPlies = 6;
418 char white_holding[64], black_holding[64];
419 TimeMark lastNodeCountTime;
420 long lastNodeCount=0;
421 int have_sent_ICS_logon = 0;
422 int movesPerSession;
423 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
424 long timeControl_2; /* [AS] Allow separate time controls */
425 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
426 long timeRemaining[2][MAX_MOVES];
427 int matchGame = 0;
428 TimeMark programStartTime;
429 char ics_handle[MSG_SIZ];
430 int have_set_title = 0;
431
432 /* animateTraining preserves the state of appData.animate
433  * when Training mode is activated. This allows the
434  * response to be animated when appData.animate == TRUE and
435  * appData.animateDragging == TRUE.
436  */
437 Boolean animateTraining;
438
439 GameInfo gameInfo;
440
441 AppData appData;
442
443 Board boards[MAX_MOVES];
444 /* [HGM] Following 7 needed for accurate legality tests: */
445 signed char  epStatus[MAX_MOVES];
446 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
447 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
448 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
449 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
450 int   initialRulePlies, FENrulePlies;
451 char  FENepStatus;
452 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
453 int loadFlag = 0; 
454 int shuffleOpenings;
455 int mute; // mute all sounds
456
457 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
458     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
459         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
460     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
461         BlackKing, BlackBishop, BlackKnight, BlackRook }
462 };
463
464 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
465     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
466         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
467     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
468         BlackKing, BlackKing, BlackKnight, BlackRook }
469 };
470
471 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
472     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
473         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
474     { BlackRook, BlackMan, BlackBishop, BlackQueen,
475         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
476 };
477
478 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
479     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
480         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
481     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
482         BlackKing, BlackBishop, BlackKnight, BlackRook }
483 };
484
485 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
486     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
487         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
488     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
489         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
490 };
491
492
493 #if (BOARD_SIZE>=10)
494 ChessSquare ShogiArray[2][BOARD_SIZE] = {
495     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
496         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
497     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
498         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
499 };
500
501 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
502     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
503         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
505         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
506 };
507
508 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
509     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
510         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
512         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
513 };
514
515 ChessSquare GreatArray[2][BOARD_SIZE] = {
516     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
517         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
518     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
519         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
520 };
521
522 ChessSquare JanusArray[2][BOARD_SIZE] = {
523     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
524         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
525     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
526         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
527 };
528
529 #ifdef GOTHIC
530 ChessSquare GothicArray[2][BOARD_SIZE] = {
531     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
532         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
533     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
534         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
535 };
536 #else // !GOTHIC
537 #define GothicArray CapablancaArray
538 #endif // !GOTHIC
539
540 #ifdef FALCON
541 ChessSquare FalconArray[2][BOARD_SIZE] = {
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
543         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
545         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
546 };
547 #else // !FALCON
548 #define FalconArray CapablancaArray
549 #endif // !FALCON
550
551 #else // !(BOARD_SIZE>=10)
552 #define XiangqiPosition FIDEArray
553 #define CapablancaArray FIDEArray
554 #define GothicArray FIDEArray
555 #define GreatArray FIDEArray
556 #endif // !(BOARD_SIZE>=10)
557
558 #if (BOARD_SIZE>=12)
559 ChessSquare CourierArray[2][BOARD_SIZE] = {
560     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
561         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
563         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
564 };
565 #else // !(BOARD_SIZE>=12)
566 #define CourierArray CapablancaArray
567 #endif // !(BOARD_SIZE>=12)
568
569
570 Board initialPosition;
571
572
573 /* Convert str to a rating. Checks for special cases of "----",
574
575    "++++", etc. Also strips ()'s */
576 int
577 string_to_rating(str)
578   char *str;
579 {
580   while(*str && !isdigit(*str)) ++str;
581   if (!*str)
582     return 0;   /* One of the special "no rating" cases */
583   else
584     return atoi(str);
585 }
586
587 void
588 ClearProgramStats()
589 {
590     /* Init programStats */
591     programStats.movelist[0] = 0;
592     programStats.depth = 0;
593     programStats.nr_moves = 0;
594     programStats.moves_left = 0;
595     programStats.nodes = 0;
596     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
597     programStats.score = 0;
598     programStats.got_only_move = 0;
599     programStats.got_fail = 0;
600     programStats.line_is_book = 0;
601 }
602
603 void
604 InitBackEnd1()
605 {
606     int matched, min, sec;
607
608     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
609
610     GetTimeMark(&programStartTime);
611     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
612
613     ClearProgramStats();
614     programStats.ok_to_send = 1;
615     programStats.seen_stat = 0;
616
617     /*
618      * Initialize game list
619      */
620     ListNew(&gameList);
621
622
623     /*
624      * Internet chess server status
625      */
626     if (appData.icsActive) {
627         appData.matchMode = FALSE;
628         appData.matchGames = 0;
629 #if ZIPPY       
630         appData.noChessProgram = !appData.zippyPlay;
631 #else
632         appData.zippyPlay = FALSE;
633         appData.zippyTalk = FALSE;
634         appData.noChessProgram = TRUE;
635 #endif
636         if (*appData.icsHelper != NULLCHAR) {
637             appData.useTelnet = TRUE;
638             appData.telnetProgram = appData.icsHelper;
639         }
640     } else {
641         appData.zippyTalk = appData.zippyPlay = FALSE;
642     }
643
644     /* [AS] Initialize pv info list [HGM] and game state */
645     {
646         int i, j;
647
648         for( i=0; i<MAX_MOVES; i++ ) {
649             pvInfoList[i].depth = -1;
650             epStatus[i]=EP_NONE;
651             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
652         }
653     }
654
655     /*
656      * Parse timeControl resource
657      */
658     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
659                           appData.movesPerSession)) {
660         char buf[MSG_SIZ];
661         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
662         DisplayFatalError(buf, 0, 2);
663     }
664
665     /*
666      * Parse searchTime resource
667      */
668     if (*appData.searchTime != NULLCHAR) {
669         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
670         if (matched == 1) {
671             searchTime = min * 60;
672         } else if (matched == 2) {
673             searchTime = min * 60 + sec;
674         } else {
675             char buf[MSG_SIZ];
676             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
677             DisplayFatalError(buf, 0, 2);
678         }
679     }
680
681     /* [AS] Adjudication threshold */
682     adjudicateLossThreshold = appData.adjudicateLossThreshold;
683     
684     first.which = "first";
685     second.which = "second";
686     first.maybeThinking = second.maybeThinking = FALSE;
687     first.pr = second.pr = NoProc;
688     first.isr = second.isr = NULL;
689     first.sendTime = second.sendTime = 2;
690     first.sendDrawOffers = 1;
691     if (appData.firstPlaysBlack) {
692         first.twoMachinesColor = "black\n";
693         second.twoMachinesColor = "white\n";
694     } else {
695         first.twoMachinesColor = "white\n";
696         second.twoMachinesColor = "black\n";
697     }
698     first.program = appData.firstChessProgram;
699     second.program = appData.secondChessProgram;
700     first.host = appData.firstHost;
701     second.host = appData.secondHost;
702     first.dir = appData.firstDirectory;
703     second.dir = appData.secondDirectory;
704     first.other = &second;
705     second.other = &first;
706     first.initString = appData.initString;
707     second.initString = appData.secondInitString;
708     first.computerString = appData.firstComputerString;
709     second.computerString = appData.secondComputerString;
710     first.useSigint = second.useSigint = TRUE;
711     first.useSigterm = second.useSigterm = TRUE;
712     first.reuse = appData.reuseFirst;
713     second.reuse = appData.reuseSecond;
714     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
715     second.nps = appData.secondNPS;
716     first.useSetboard = second.useSetboard = FALSE;
717     first.useSAN = second.useSAN = FALSE;
718     first.usePing = second.usePing = FALSE;
719     first.lastPing = second.lastPing = 0;
720     first.lastPong = second.lastPong = 0;
721     first.usePlayother = second.usePlayother = FALSE;
722     first.useColors = second.useColors = TRUE;
723     first.useUsermove = second.useUsermove = FALSE;
724     first.sendICS = second.sendICS = FALSE;
725     first.sendName = second.sendName = appData.icsActive;
726     first.sdKludge = second.sdKludge = FALSE;
727     first.stKludge = second.stKludge = FALSE;
728     TidyProgramName(first.program, first.host, first.tidy);
729     TidyProgramName(second.program, second.host, second.tidy);
730     first.matchWins = second.matchWins = 0;
731     strcpy(first.variants, appData.variant);
732     strcpy(second.variants, appData.variant);
733     first.analysisSupport = second.analysisSupport = 2; /* detect */
734     first.analyzing = second.analyzing = FALSE;
735     first.initDone = second.initDone = FALSE;
736
737     /* New features added by Tord: */
738     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
739     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
740     /* End of new features added by Tord. */
741     first.fenOverride  = appData.fenOverride1;
742     second.fenOverride = appData.fenOverride2;
743
744     /* [HGM] time odds: set factor for each machine */
745     first.timeOdds  = appData.firstTimeOdds;
746     second.timeOdds = appData.secondTimeOdds;
747     { int norm = 1;
748         if(appData.timeOddsMode) {
749             norm = first.timeOdds;
750             if(norm > second.timeOdds) norm = second.timeOdds;
751         }
752         first.timeOdds /= norm;
753         second.timeOdds /= norm;
754     }
755
756     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
757     first.accumulateTC = appData.firstAccumulateTC;
758     second.accumulateTC = appData.secondAccumulateTC;
759     first.maxNrOfSessions = second.maxNrOfSessions = 1;
760
761     /* [HGM] debug */
762     first.debug = second.debug = FALSE;
763     first.supportsNPS = second.supportsNPS = UNKNOWN;
764
765     /* [HGM] options */
766     first.optionSettings  = appData.firstOptions;
767     second.optionSettings = appData.secondOptions;
768
769     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
770     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
771     first.isUCI = appData.firstIsUCI; /* [AS] */
772     second.isUCI = appData.secondIsUCI; /* [AS] */
773     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
774     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
775
776     if (appData.firstProtocolVersion > PROTOVER ||
777         appData.firstProtocolVersion < 1) {
778       char buf[MSG_SIZ];
779       sprintf(buf, _("protocol version %d not supported"),
780               appData.firstProtocolVersion);
781       DisplayFatalError(buf, 0, 2);
782     } else {
783       first.protocolVersion = appData.firstProtocolVersion;
784     }
785
786     if (appData.secondProtocolVersion > PROTOVER ||
787         appData.secondProtocolVersion < 1) {
788       char buf[MSG_SIZ];
789       sprintf(buf, _("protocol version %d not supported"),
790               appData.secondProtocolVersion);
791       DisplayFatalError(buf, 0, 2);
792     } else {
793       second.protocolVersion = appData.secondProtocolVersion;
794     }
795
796     if (appData.icsActive) {
797         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
798     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
799         appData.clockMode = FALSE;
800         first.sendTime = second.sendTime = 0;
801     }
802     
803 #if ZIPPY
804     /* Override some settings from environment variables, for backward
805        compatibility.  Unfortunately it's not feasible to have the env
806        vars just set defaults, at least in xboard.  Ugh.
807     */
808     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
809       ZippyInit();
810     }
811 #endif
812     
813     if (appData.noChessProgram) {
814         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
815         sprintf(programVersion, "%s", PACKAGE_STRING);
816     } else {
817       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
818       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
819       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
820     }
821
822     if (!appData.icsActive) {
823       char buf[MSG_SIZ];
824       /* Check for variants that are supported only in ICS mode,
825          or not at all.  Some that are accepted here nevertheless
826          have bugs; see comments below.
827       */
828       VariantClass variant = StringToVariant(appData.variant);
829       switch (variant) {
830       case VariantBughouse:     /* need four players and two boards */
831       case VariantKriegspiel:   /* need to hide pieces and move details */
832       /* case VariantFischeRandom: (Fabien: moved below) */
833         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
834         DisplayFatalError(buf, 0, 2);
835         return;
836
837       case VariantUnknown:
838       case VariantLoadable:
839       case Variant29:
840       case Variant30:
841       case Variant31:
842       case Variant32:
843       case Variant33:
844       case Variant34:
845       case Variant35:
846       case Variant36:
847       default:
848         sprintf(buf, _("Unknown variant name %s"), appData.variant);
849         DisplayFatalError(buf, 0, 2);
850         return;
851
852       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
853       case VariantFairy:      /* [HGM] TestLegality definitely off! */
854       case VariantGothic:     /* [HGM] should work */
855       case VariantCapablanca: /* [HGM] should work */
856       case VariantCourier:    /* [HGM] initial forced moves not implemented */
857       case VariantShogi:      /* [HGM] drops not tested for legality */
858       case VariantKnightmate: /* [HGM] should work */
859       case VariantCylinder:   /* [HGM] untested */
860       case VariantFalcon:     /* [HGM] untested */
861       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
862                                  offboard interposition not understood */
863       case VariantNormal:     /* definitely works! */
864       case VariantWildCastle: /* pieces not automatically shuffled */
865       case VariantNoCastle:   /* pieces not automatically shuffled */
866       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
867       case VariantLosers:     /* should work except for win condition,
868                                  and doesn't know captures are mandatory */
869       case VariantSuicide:    /* should work except for win condition,
870                                  and doesn't know captures are mandatory */
871       case VariantGiveaway:   /* should work except for win condition,
872                                  and doesn't know captures are mandatory */
873       case VariantTwoKings:   /* should work */
874       case VariantAtomic:     /* should work except for win condition */
875       case Variant3Check:     /* should work except for win condition */
876       case VariantShatranj:   /* should work except for all win conditions */
877       case VariantBerolina:   /* might work if TestLegality is off */
878       case VariantCapaRandom: /* should work */
879       case VariantJanus:      /* should work */
880       case VariantSuper:      /* experimental */
881       case VariantGreat:      /* experimental, requires legality testing to be off */
882         break;
883       }
884     }
885
886     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
887     InitEngineUCI( installDir, &second );
888 }
889
890 int NextIntegerFromString( char ** str, long * value )
891 {
892     int result = -1;
893     char * s = *str;
894
895     while( *s == ' ' || *s == '\t' ) {
896         s++;
897     }
898
899     *value = 0;
900
901     if( *s >= '0' && *s <= '9' ) {
902         while( *s >= '0' && *s <= '9' ) {
903             *value = *value * 10 + (*s - '0');
904             s++;
905         }
906
907         result = 0;
908     }
909
910     *str = s;
911
912     return result;
913 }
914
915 int NextTimeControlFromString( char ** str, long * value )
916 {
917     long temp;
918     int result = NextIntegerFromString( str, &temp );
919
920     if( result == 0 ) {
921         *value = temp * 60; /* Minutes */
922         if( **str == ':' ) {
923             (*str)++;
924             result = NextIntegerFromString( str, &temp );
925             *value += temp; /* Seconds */
926         }
927     }
928
929     return result;
930 }
931
932 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
933 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
934     int result = -1; long temp, temp2;
935
936     if(**str != '+') return -1; // old params remain in force!
937     (*str)++;
938     if( NextTimeControlFromString( str, &temp ) ) return -1;
939
940     if(**str != '/') {
941         /* time only: incremental or sudden-death time control */
942         if(**str == '+') { /* increment follows; read it */
943             (*str)++;
944             if(result = NextIntegerFromString( str, &temp2)) return -1;
945             *inc = temp2 * 1000;
946         } else *inc = 0;
947         *moves = 0; *tc = temp * 1000; 
948         return 0;
949     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
950
951     (*str)++; /* classical time control */
952     result = NextTimeControlFromString( str, &temp2);
953     if(result == 0) {
954         *moves = temp/60;
955         *tc    = temp2 * 1000;
956         *inc   = 0;
957     }
958     return result;
959 }
960
961 int GetTimeQuota(int movenr)
962 {   /* [HGM] get time to add from the multi-session time-control string */
963     int moves=1; /* kludge to force reading of first session */
964     long time, increment;
965     char *s = fullTimeControlString;
966
967     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
968     do {
969         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
970         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
971         if(movenr == -1) return time;    /* last move before new session     */
972         if(!moves) return increment;     /* current session is incremental   */
973         if(movenr >= 0) movenr -= moves; /* we already finished this session */
974     } while(movenr >= -1);               /* try again for next session       */
975
976     return 0; // no new time quota on this move
977 }
978
979 int
980 ParseTimeControl(tc, ti, mps)
981      char *tc;
982      int ti;
983      int mps;
984 {
985   long tc1;
986   long tc2;
987   char buf[MSG_SIZ];
988   
989   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
990   if(ti > 0) {
991     if(mps)
992       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
993     else sprintf(buf, "+%s+%d", tc, ti);
994   } else {
995     if(mps)
996              sprintf(buf, "+%d/%s", mps, tc);
997     else sprintf(buf, "+%s", tc);
998   }
999   fullTimeControlString = StrSave(buf);
1000   
1001   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1002     return FALSE;
1003   }
1004   
1005   if( *tc == '/' ) {
1006     /* Parse second time control */
1007     tc++;
1008     
1009     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1010       return FALSE;
1011     }
1012     
1013     if( tc2 == 0 ) {
1014       return FALSE;
1015     }
1016     
1017     timeControl_2 = tc2 * 1000;
1018   }
1019   else {
1020     timeControl_2 = 0;
1021   }
1022   
1023   if( tc1 == 0 ) {
1024     return FALSE;
1025   }
1026   
1027   timeControl = tc1 * 1000;
1028   
1029   if (ti >= 0) {
1030     timeIncrement = ti * 1000;  /* convert to ms */
1031     movesPerSession = 0;
1032   } else {
1033     timeIncrement = 0;
1034     movesPerSession = mps;
1035   }
1036   return TRUE;
1037 }
1038
1039 void
1040 InitBackEnd2()
1041 {
1042     if (appData.debugMode) {
1043         fprintf(debugFP, "%s\n", programVersion);
1044     }
1045
1046     if (appData.matchGames > 0) {
1047         appData.matchMode = TRUE;
1048     } else if (appData.matchMode) {
1049         appData.matchGames = 1;
1050     }
1051     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052         appData.matchGames = appData.sameColorGames;
1053     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1056     }
1057     Reset(TRUE, FALSE);
1058     if (appData.noChessProgram || first.protocolVersion == 1) {
1059       InitBackEnd3();
1060     } else {
1061       /* kludge: allow timeout for initial "feature" commands */
1062       FreezeUI();
1063       DisplayMessage("", _("Starting chess program"));
1064       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1065     }
1066 }
1067
1068 void
1069 InitBackEnd3 P((void))
1070 {
1071     GameMode initialMode;
1072     char buf[MSG_SIZ];
1073     int err;
1074
1075     InitChessProgram(&first, startedFromSetupPosition);
1076
1077
1078     if (appData.icsActive) {
1079 #ifdef WIN32
1080         /* [DM] Make a console window if needed [HGM] merged ifs */
1081         ConsoleCreate(); 
1082 #endif
1083         err = establish();
1084         if (err != 0) {
1085             if (*appData.icsCommPort != NULLCHAR) {
1086                 sprintf(buf, _("Could not open comm port %s"),  
1087                         appData.icsCommPort);
1088             } else {
1089                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1090                         appData.icsHost, appData.icsPort);
1091             }
1092             DisplayFatalError(buf, err, 1);
1093             return;
1094         }
1095         SetICSMode();
1096         telnetISR =
1097           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098         fromUserISR =
1099           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100     } else if (appData.noChessProgram) {
1101         SetNCPMode();
1102     } else {
1103         SetGNUMode();
1104     }
1105
1106     if (*appData.cmailGameName != NULLCHAR) {
1107         SetCmailMode();
1108         OpenLoopback(&cmailPR);
1109         cmailISR =
1110           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1111     }
1112     
1113     ThawUI();
1114     DisplayMessage("", "");
1115     if (StrCaseCmp(appData.initialMode, "") == 0) {
1116       initialMode = BeginningOfGame;
1117     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118       initialMode = TwoMachinesPlay;
1119     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120       initialMode = AnalyzeFile; 
1121     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122       initialMode = AnalyzeMode;
1123     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124       initialMode = MachinePlaysWhite;
1125     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126       initialMode = MachinePlaysBlack;
1127     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128       initialMode = EditGame;
1129     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130       initialMode = EditPosition;
1131     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132       initialMode = Training;
1133     } else {
1134       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135       DisplayFatalError(buf, 0, 2);
1136       return;
1137     }
1138
1139     if (appData.matchMode) {
1140         /* Set up machine vs. machine match */
1141         if (appData.noChessProgram) {
1142             DisplayFatalError(_("Can't have a match with no chess programs"),
1143                               0, 2);
1144             return;
1145         }
1146         matchMode = TRUE;
1147         matchGame = 1;
1148         if (*appData.loadGameFile != NULLCHAR) {
1149             int index = appData.loadGameIndex; // [HGM] autoinc
1150             if(index<0) lastIndex = index = 1;
1151             if (!LoadGameFromFile(appData.loadGameFile,
1152                                   index,
1153                                   appData.loadGameFile, FALSE)) {
1154                 DisplayFatalError(_("Bad game file"), 0, 1);
1155                 return;
1156             }
1157         } else if (*appData.loadPositionFile != NULLCHAR) {
1158             int index = appData.loadPositionIndex; // [HGM] autoinc
1159             if(index<0) lastIndex = index = 1;
1160             if (!LoadPositionFromFile(appData.loadPositionFile,
1161                                       index,
1162                                       appData.loadPositionFile)) {
1163                 DisplayFatalError(_("Bad position file"), 0, 1);
1164                 return;
1165             }
1166         }
1167         TwoMachinesEvent();
1168     } else if (*appData.cmailGameName != NULLCHAR) {
1169         /* Set up cmail mode */
1170         ReloadCmailMsgEvent(TRUE);
1171     } else {
1172         /* Set up other modes */
1173         if (initialMode == AnalyzeFile) {
1174           if (*appData.loadGameFile == NULLCHAR) {
1175             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1176             return;
1177           }
1178         }
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             (void) LoadGameFromFile(appData.loadGameFile,
1181                                     appData.loadGameIndex,
1182                                     appData.loadGameFile, TRUE);
1183         } else if (*appData.loadPositionFile != NULLCHAR) {
1184             (void) LoadPositionFromFile(appData.loadPositionFile,
1185                                         appData.loadPositionIndex,
1186                                         appData.loadPositionFile);
1187             /* [HGM] try to make self-starting even after FEN load */
1188             /* to allow automatic setup of fairy variants with wtm */
1189             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190                 gameMode = BeginningOfGame;
1191                 setboardSpoiledMachineBlack = 1;
1192             }
1193             /* [HGM] loadPos: make that every new game uses the setup */
1194             /* from file as long as we do not switch variant          */
1195             if(!blackPlaysFirst) { int i;
1196                 startedFromPositionFile = TRUE;
1197                 CopyBoard(filePosition, boards[0]);
1198                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1199             }
1200         }
1201         if (initialMode == AnalyzeMode) {
1202           if (appData.noChessProgram) {
1203             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1204             return;
1205           }
1206           if (appData.icsActive) {
1207             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1208             return;
1209           }
1210           AnalyzeModeEvent();
1211         } else if (initialMode == AnalyzeFile) {
1212           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213           ShowThinkingEvent();
1214           AnalyzeFileEvent();
1215           AnalysisPeriodicEvent(1);
1216         } else if (initialMode == MachinePlaysWhite) {
1217           if (appData.noChessProgram) {
1218             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1219                               0, 2);
1220             return;
1221           }
1222           if (appData.icsActive) {
1223             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1224                               0, 2);
1225             return;
1226           }
1227           MachineWhiteEvent();
1228         } else if (initialMode == MachinePlaysBlack) {
1229           if (appData.noChessProgram) {
1230             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1231                               0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1236                               0, 2);
1237             return;
1238           }
1239           MachineBlackEvent();
1240         } else if (initialMode == TwoMachinesPlay) {
1241           if (appData.noChessProgram) {
1242             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1243                               0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1248                               0, 2);
1249             return;
1250           }
1251           TwoMachinesEvent();
1252         } else if (initialMode == EditGame) {
1253           EditGameEvent();
1254         } else if (initialMode == EditPosition) {
1255           EditPositionEvent();
1256         } else if (initialMode == Training) {
1257           if (*appData.loadGameFile == NULLCHAR) {
1258             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1259             return;
1260           }
1261           TrainingEvent();
1262         }
1263     }
1264 }
1265
1266 /*
1267  * Establish will establish a contact to a remote host.port.
1268  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269  *  used to talk to the host.
1270  * Returns 0 if okay, error code if not.
1271  */
1272 int
1273 establish()
1274 {
1275     char buf[MSG_SIZ];
1276
1277     if (*appData.icsCommPort != NULLCHAR) {
1278         /* Talk to the host through a serial comm port */
1279         return OpenCommPort(appData.icsCommPort, &icsPR);
1280
1281     } else if (*appData.gateway != NULLCHAR) {
1282         if (*appData.remoteShell == NULLCHAR) {
1283             /* Use the rcmd protocol to run telnet program on a gateway host */
1284             snprintf(buf, sizeof(buf), "%s %s %s",
1285                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1286             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1287
1288         } else {
1289             /* Use the rsh program to run telnet program on a gateway host */
1290             if (*appData.remoteUser == NULLCHAR) {
1291                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292                         appData.gateway, appData.telnetProgram,
1293                         appData.icsHost, appData.icsPort);
1294             } else {
1295                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296                         appData.remoteShell, appData.gateway, 
1297                         appData.remoteUser, appData.telnetProgram,
1298                         appData.icsHost, appData.icsPort);
1299             }
1300             return StartChildProcess(buf, "", &icsPR);
1301
1302         }
1303     } else if (appData.useTelnet) {
1304         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1305
1306     } else {
1307         /* TCP socket interface differs somewhat between
1308            Unix and NT; handle details in the front end.
1309            */
1310         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1311     }
1312 }
1313
1314 void
1315 show_bytes(fp, buf, count)
1316      FILE *fp;
1317      char *buf;
1318      int count;
1319 {
1320     while (count--) {
1321         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322             fprintf(fp, "\\%03o", *buf & 0xff);
1323         } else {
1324             putc(*buf, fp);
1325         }
1326         buf++;
1327     }
1328     fflush(fp);
1329 }
1330
1331 /* Returns an errno value */
1332 int
1333 OutputMaybeTelnet(pr, message, count, outError)
1334      ProcRef pr;
1335      char *message;
1336      int count;
1337      int *outError;
1338 {
1339     char buf[8192], *p, *q, *buflim;
1340     int left, newcount, outcount;
1341
1342     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343         *appData.gateway != NULLCHAR) {
1344         if (appData.debugMode) {
1345             fprintf(debugFP, ">ICS: ");
1346             show_bytes(debugFP, message, count);
1347             fprintf(debugFP, "\n");
1348         }
1349         return OutputToProcess(pr, message, count, outError);
1350     }
1351
1352     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1353     p = message;
1354     q = buf;
1355     left = count;
1356     newcount = 0;
1357     while (left) {
1358         if (q >= buflim) {
1359             if (appData.debugMode) {
1360                 fprintf(debugFP, ">ICS: ");
1361                 show_bytes(debugFP, buf, newcount);
1362                 fprintf(debugFP, "\n");
1363             }
1364             outcount = OutputToProcess(pr, buf, newcount, outError);
1365             if (outcount < newcount) return -1; /* to be sure */
1366             q = buf;
1367             newcount = 0;
1368         }
1369         if (*p == '\n') {
1370             *q++ = '\r';
1371             newcount++;
1372         } else if (((unsigned char) *p) == TN_IAC) {
1373             *q++ = (char) TN_IAC;
1374             newcount ++;
1375         }
1376         *q++ = *p++;
1377         newcount++;
1378         left--;
1379     }
1380     if (appData.debugMode) {
1381         fprintf(debugFP, ">ICS: ");
1382         show_bytes(debugFP, buf, newcount);
1383         fprintf(debugFP, "\n");
1384     }
1385     outcount = OutputToProcess(pr, buf, newcount, outError);
1386     if (outcount < newcount) return -1; /* to be sure */
1387     return count;
1388 }
1389
1390 void
1391 read_from_player(isr, closure, message, count, error)
1392      InputSourceRef isr;
1393      VOIDSTAR closure;
1394      char *message;
1395      int count;
1396      int error;
1397 {
1398     int outError, outCount;
1399     static int gotEof = 0;
1400
1401     /* Pass data read from player on to ICS */
1402     if (count > 0) {
1403         gotEof = 0;
1404         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405         if (outCount < count) {
1406             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407         }
1408     } else if (count < 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411     } else if (gotEof++ > 0) {
1412         RemoveInputSource(isr);
1413         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1414     }
1415 }
1416
1417 void
1418 KeepAlive()
1419 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420     SendToICS("date\n");
1421     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1422 }
1423
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1426 {
1427     char buffer[MSG_SIZ];
1428     va_list args;
1429
1430     va_start(args, format);
1431     vsnprintf(buffer, sizeof(buffer), format, args);
1432     buffer[sizeof(buffer)-1] = '\0';
1433     SendToICS(buffer);
1434     va_end(args);
1435 }
1436
1437 void
1438 SendToICS(s)
1439      char *s;
1440 {
1441     int count, outCount, outError;
1442
1443     if (icsPR == NULL) return;
1444
1445     count = strlen(s);
1446     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447     if (outCount < count) {
1448         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449     }
1450 }
1451
1452 /* This is used for sending logon scripts to the ICS. Sending
1453    without a delay causes problems when using timestamp on ICC
1454    (at least on my machine). */
1455 void
1456 SendToICSDelayed(s,msdelay)
1457      char *s;
1458      long msdelay;
1459 {
1460     int count, outCount, outError;
1461
1462     if (icsPR == NULL) return;
1463
1464     count = strlen(s);
1465     if (appData.debugMode) {
1466         fprintf(debugFP, ">ICS: ");
1467         show_bytes(debugFP, s, count);
1468         fprintf(debugFP, "\n");
1469     }
1470     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1471                                       msdelay);
1472     if (outCount < count) {
1473         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1474     }
1475 }
1476
1477
1478 /* Remove all highlighting escape sequences in s
1479    Also deletes any suffix starting with '(' 
1480    */
1481 char *
1482 StripHighlightAndTitle(s)
1483      char *s;
1484 {
1485     static char retbuf[MSG_SIZ];
1486     char *p = retbuf;
1487
1488     while (*s != NULLCHAR) {
1489         while (*s == '\033') {
1490             while (*s != NULLCHAR && !isalpha(*s)) s++;
1491             if (*s != NULLCHAR) s++;
1492         }
1493         while (*s != NULLCHAR && *s != '\033') {
1494             if (*s == '(' || *s == '[') {
1495                 *p = NULLCHAR;
1496                 return retbuf;
1497             }
1498             *p++ = *s++;
1499         }
1500     }
1501     *p = NULLCHAR;
1502     return retbuf;
1503 }
1504
1505 /* Remove all highlighting escape sequences in s */
1506 char *
1507 StripHighlight(s)
1508      char *s;
1509 {
1510     static char retbuf[MSG_SIZ];
1511     char *p = retbuf;
1512
1513     while (*s != NULLCHAR) {
1514         while (*s == '\033') {
1515             while (*s != NULLCHAR && !isalpha(*s)) s++;
1516             if (*s != NULLCHAR) s++;
1517         }
1518         while (*s != NULLCHAR && *s != '\033') {
1519             *p++ = *s++;
1520         }
1521     }
1522     *p = NULLCHAR;
1523     return retbuf;
1524 }
1525
1526 char *variantNames[] = VARIANT_NAMES;
1527 char *
1528 VariantName(v)
1529      VariantClass v;
1530 {
1531     return variantNames[v];
1532 }
1533
1534
1535 /* Identify a variant from the strings the chess servers use or the
1536    PGN Variant tag names we use. */
1537 VariantClass
1538 StringToVariant(e)
1539      char *e;
1540 {
1541     char *p;
1542     int wnum = -1;
1543     VariantClass v = VariantNormal;
1544     int i, found = FALSE;
1545     char buf[MSG_SIZ];
1546
1547     if (!e) return v;
1548
1549     /* [HGM] skip over optional board-size prefixes */
1550     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552         while( *e++ != '_');
1553     }
1554
1555     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1556         v = VariantNormal;
1557         found = TRUE;
1558     } else
1559     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560       if (StrCaseStr(e, variantNames[i])) {
1561         v = (VariantClass) i;
1562         found = TRUE;
1563         break;
1564       }
1565     }
1566
1567     if (!found) {
1568       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569           || StrCaseStr(e, "wild/fr") 
1570           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571         v = VariantFischeRandom;
1572       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573                  (i = 1, p = StrCaseStr(e, "w"))) {
1574         p += i;
1575         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1576         if (isdigit(*p)) {
1577           wnum = atoi(p);
1578         } else {
1579           wnum = -1;
1580         }
1581         switch (wnum) {
1582         case 0: /* FICS only, actually */
1583         case 1:
1584           /* Castling legal even if K starts on d-file */
1585           v = VariantWildCastle;
1586           break;
1587         case 2:
1588         case 3:
1589         case 4:
1590           /* Castling illegal even if K & R happen to start in
1591              normal positions. */
1592           v = VariantNoCastle;
1593           break;
1594         case 5:
1595         case 7:
1596         case 8:
1597         case 10:
1598         case 11:
1599         case 12:
1600         case 13:
1601         case 14:
1602         case 15:
1603         case 18:
1604         case 19:
1605           /* Castling legal iff K & R start in normal positions */
1606           v = VariantNormal;
1607           break;
1608         case 6:
1609         case 20:
1610         case 21:
1611           /* Special wilds for position setup; unclear what to do here */
1612           v = VariantLoadable;
1613           break;
1614         case 9:
1615           /* Bizarre ICC game */
1616           v = VariantTwoKings;
1617           break;
1618         case 16:
1619           v = VariantKriegspiel;
1620           break;
1621         case 17:
1622           v = VariantLosers;
1623           break;
1624         case 22:
1625           v = VariantFischeRandom;
1626           break;
1627         case 23:
1628           v = VariantCrazyhouse;
1629           break;
1630         case 24:
1631           v = VariantBughouse;
1632           break;
1633         case 25:
1634           v = Variant3Check;
1635           break;
1636         case 26:
1637           /* Not quite the same as FICS suicide! */
1638           v = VariantGiveaway;
1639           break;
1640         case 27:
1641           v = VariantAtomic;
1642           break;
1643         case 28:
1644           v = VariantShatranj;
1645           break;
1646
1647         /* Temporary names for future ICC types.  The name *will* change in 
1648            the next xboard/WinBoard release after ICC defines it. */
1649         case 29:
1650           v = Variant29;
1651           break;
1652         case 30:
1653           v = Variant30;
1654           break;
1655         case 31:
1656           v = Variant31;
1657           break;
1658         case 32:
1659           v = Variant32;
1660           break;
1661         case 33:
1662           v = Variant33;
1663           break;
1664         case 34:
1665           v = Variant34;
1666           break;
1667         case 35:
1668           v = Variant35;
1669           break;
1670         case 36:
1671           v = Variant36;
1672           break;
1673         case 37:
1674           v = VariantShogi;
1675           break;
1676         case 38:
1677           v = VariantXiangqi;
1678           break;
1679         case 39:
1680           v = VariantCourier;
1681           break;
1682         case 40:
1683           v = VariantGothic;
1684           break;
1685         case 41:
1686           v = VariantCapablanca;
1687           break;
1688         case 42:
1689           v = VariantKnightmate;
1690           break;
1691         case 43:
1692           v = VariantFairy;
1693           break;
1694         case 44:
1695           v = VariantCylinder;
1696           break;
1697         case 45:
1698           v = VariantFalcon;
1699           break;
1700         case 46:
1701           v = VariantCapaRandom;
1702           break;
1703         case 47:
1704           v = VariantBerolina;
1705           break;
1706         case 48:
1707           v = VariantJanus;
1708           break;
1709         case 49:
1710           v = VariantSuper;
1711           break;
1712         case 50:
1713           v = VariantGreat;
1714           break;
1715         case -1:
1716           /* Found "wild" or "w" in the string but no number;
1717              must assume it's normal chess. */
1718           v = VariantNormal;
1719           break;
1720         default:
1721           sprintf(buf, _("Unknown wild type %d"), wnum);
1722           DisplayError(buf, 0);
1723           v = VariantUnknown;
1724           break;
1725         }
1726       }
1727     }
1728     if (appData.debugMode) {
1729       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730               e, wnum, VariantName(v));
1731     }
1732     return v;
1733 }
1734
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1737
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739    advance *index beyond it, and set leftover_start to the new value of
1740    *index; else return FALSE.  If pattern contains the character '*', it
1741    matches any sequence of characters not containing '\r', '\n', or the
1742    character following the '*' (if any), and the matched sequence(s) are
1743    copied into star_match.
1744    */
1745 int
1746 looking_at(buf, index, pattern)
1747      char *buf;
1748      int *index;
1749      char *pattern;
1750 {
1751     char *bufp = &buf[*index], *patternp = pattern;
1752     int star_count = 0;
1753     char *matchp = star_match[0];
1754     
1755     for (;;) {
1756         if (*patternp == NULLCHAR) {
1757             *index = leftover_start = bufp - buf;
1758             *matchp = NULLCHAR;
1759             return TRUE;
1760         }
1761         if (*bufp == NULLCHAR) return FALSE;
1762         if (*patternp == '*') {
1763             if (*bufp == *(patternp + 1)) {
1764                 *matchp = NULLCHAR;
1765                 matchp = star_match[++star_count];
1766                 patternp += 2;
1767                 bufp++;
1768                 continue;
1769             } else if (*bufp == '\n' || *bufp == '\r') {
1770                 patternp++;
1771                 if (*patternp == NULLCHAR)
1772                   continue;
1773                 else
1774                   return FALSE;
1775             } else {
1776                 *matchp++ = *bufp++;
1777                 continue;
1778             }
1779         }
1780         if (*patternp != *bufp) return FALSE;
1781         patternp++;
1782         bufp++;
1783     }
1784 }
1785
1786 void
1787 SendToPlayer(data, length)
1788      char *data;
1789      int length;
1790 {
1791     int error, outCount;
1792     outCount = OutputToProcess(NoProc, data, length, &error);
1793     if (outCount < length) {
1794         DisplayFatalError(_("Error writing to display"), error, 1);
1795     }
1796 }
1797
1798 void
1799 PackHolding(packed, holding)
1800      char packed[];
1801      char *holding;
1802 {
1803     char *p = holding;
1804     char *q = packed;
1805     int runlength = 0;
1806     int curr = 9999;
1807     do {
1808         if (*p == curr) {
1809             runlength++;
1810         } else {
1811             switch (runlength) {
1812               case 0:
1813                 break;
1814               case 1:
1815                 *q++ = curr;
1816                 break;
1817               case 2:
1818                 *q++ = curr;
1819                 *q++ = curr;
1820                 break;
1821               default:
1822                 sprintf(q, "%d", runlength);
1823                 while (*q) q++;
1824                 *q++ = curr;
1825                 break;
1826             }
1827             runlength = 1;
1828             curr = *p;
1829         }
1830     } while (*p++);
1831     *q = NULLCHAR;
1832 }
1833
1834 /* Telnet protocol requests from the front end */
1835 void
1836 TelnetRequest(ddww, option)
1837      unsigned char ddww, option;
1838 {
1839     unsigned char msg[3];
1840     int outCount, outError;
1841
1842     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1843
1844     if (appData.debugMode) {
1845         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1846         switch (ddww) {
1847           case TN_DO:
1848             ddwwStr = "DO";
1849             break;
1850           case TN_DONT:
1851             ddwwStr = "DONT";
1852             break;
1853           case TN_WILL:
1854             ddwwStr = "WILL";
1855             break;
1856           case TN_WONT:
1857             ddwwStr = "WONT";
1858             break;
1859           default:
1860             ddwwStr = buf1;
1861             sprintf(buf1, "%d", ddww);
1862             break;
1863         }
1864         switch (option) {
1865           case TN_ECHO:
1866             optionStr = "ECHO";
1867             break;
1868           default:
1869             optionStr = buf2;
1870             sprintf(buf2, "%d", option);
1871             break;
1872         }
1873         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1874     }
1875     msg[0] = TN_IAC;
1876     msg[1] = ddww;
1877     msg[2] = option;
1878     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1879     if (outCount < 3) {
1880         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881     }
1882 }
1883
1884 void
1885 DoEcho()
1886 {
1887     if (!appData.icsActive) return;
1888     TelnetRequest(TN_DO, TN_ECHO);
1889 }
1890
1891 void
1892 DontEcho()
1893 {
1894     if (!appData.icsActive) return;
1895     TelnetRequest(TN_DONT, TN_ECHO);
1896 }
1897
1898 void
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1900 {
1901     /* put the holdings sent to us by the server on the board holdings area */
1902     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1903     char p;
1904     ChessSquare piece;
1905
1906     if(gameInfo.holdingsWidth < 2)  return;
1907
1908     if( (int)lowestPiece >= BlackPawn ) {
1909         holdingsColumn = 0;
1910         countsColumn = 1;
1911         holdingsStartRow = BOARD_HEIGHT-1;
1912         direction = -1;
1913     } else {
1914         holdingsColumn = BOARD_WIDTH-1;
1915         countsColumn = BOARD_WIDTH-2;
1916         holdingsStartRow = 0;
1917         direction = 1;
1918     }
1919
1920     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1921         board[i][holdingsColumn] = EmptySquare;
1922         board[i][countsColumn]   = (ChessSquare) 0;
1923     }
1924     while( (p=*holdings++) != NULLCHAR ) {
1925         piece = CharToPiece( ToUpper(p) );
1926         if(piece == EmptySquare) continue;
1927         /*j = (int) piece - (int) WhitePawn;*/
1928         j = PieceToNumber(piece);
1929         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1930         if(j < 0) continue;               /* should not happen */
1931         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1932         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1933         board[holdingsStartRow+j*direction][countsColumn]++;
1934     }
1935
1936 }
1937
1938
1939 void
1940 VariantSwitch(Board board, VariantClass newVariant)
1941 {
1942    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1943    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1944 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1945
1946    startedFromPositionFile = FALSE;
1947    if(gameInfo.variant == newVariant) return;
1948
1949    /* [HGM] This routine is called each time an assignment is made to
1950     * gameInfo.variant during a game, to make sure the board sizes
1951     * are set to match the new variant. If that means adding or deleting
1952     * holdings, we shift the playing board accordingly
1953     * This kludge is needed because in ICS observe mode, we get boards
1954     * of an ongoing game without knowing the variant, and learn about the
1955     * latter only later. This can be because of the move list we requested,
1956     * in which case the game history is refilled from the beginning anyway,
1957     * but also when receiving holdings of a crazyhouse game. In the latter
1958     * case we want to add those holdings to the already received position.
1959     */
1960
1961
1962   if (appData.debugMode) {
1963     fprintf(debugFP, "Switch board from %s to %s\n",
1964                VariantName(gameInfo.variant), VariantName(newVariant));
1965     setbuf(debugFP, NULL);
1966   }
1967     shuffleOpenings = 0;       /* [HGM] shuffle */
1968     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1969     switch(newVariant) {
1970             case VariantShogi:
1971               newWidth = 9;  newHeight = 9;
1972               gameInfo.holdingsSize = 7;
1973             case VariantBughouse:
1974             case VariantCrazyhouse:
1975               newHoldingsWidth = 2; break;
1976             default:
1977               newHoldingsWidth = gameInfo.holdingsSize = 0;
1978     }
1979
1980     if(newWidth  != gameInfo.boardWidth  ||
1981        newHeight != gameInfo.boardHeight ||
1982        newHoldingsWidth != gameInfo.holdingsWidth ) {
1983
1984         /* shift position to new playing area, if needed */
1985         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1986            for(i=0; i<BOARD_HEIGHT; i++) 
1987                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1988                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1989                                                      board[i][j];
1990            for(i=0; i<newHeight; i++) {
1991                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1992                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1993            }
1994         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1995            for(i=0; i<BOARD_HEIGHT; i++)
1996                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1997                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1998                                                  board[i][j];
1999         }
2000
2001         gameInfo.boardWidth  = newWidth;
2002         gameInfo.boardHeight = newHeight;
2003         gameInfo.holdingsWidth = newHoldingsWidth;
2004         gameInfo.variant = newVariant;
2005         InitDrawingSizes(-2, 0);
2006
2007         /* [HGM] The following should definitely be solved in a better way */
2008         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2009     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2010
2011     forwardMostMove = oldForwardMostMove;
2012     backwardMostMove = oldBackwardMostMove;
2013     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2014 }
2015
2016 static int loggedOn = FALSE;
2017
2018 /*-- Game start info cache: --*/
2019 int gs_gamenum;
2020 char gs_kind[MSG_SIZ];
2021 static char player1Name[128] = "";
2022 static char player2Name[128] = "";
2023 static int player1Rating = -1;
2024 static int player2Rating = -1;
2025 /*----------------------------*/
2026
2027 ColorClass curColor = ColorNormal;
2028 int suppressKibitz = 0;
2029
2030 void
2031 read_from_ics(isr, closure, data, count, error)
2032      InputSourceRef isr;
2033      VOIDSTAR closure;
2034      char *data;
2035      int count;
2036      int error;
2037 {
2038 #define BUF_SIZE 8192
2039 #define STARTED_NONE 0
2040 #define STARTED_MOVES 1
2041 #define STARTED_BOARD 2
2042 #define STARTED_OBSERVE 3
2043 #define STARTED_HOLDINGS 4
2044 #define STARTED_CHATTER 5
2045 #define STARTED_COMMENT 6
2046 #define STARTED_MOVES_NOHIDE 7
2047     
2048     static int started = STARTED_NONE;
2049     static char parse[20000];
2050     static int parse_pos = 0;
2051     static char buf[BUF_SIZE + 1];
2052     static int firstTime = TRUE, intfSet = FALSE;
2053     static ColorClass prevColor = ColorNormal;
2054     static int savingComment = FALSE;
2055     char str[500];
2056     int i, oldi;
2057     int buf_len;
2058     int next_out;
2059     int tkind;
2060     int backup;    /* [DM] For zippy color lines */
2061     char *p;
2062     char talker[MSG_SIZ]; // [HGM] chat
2063     int channel;
2064
2065     if (appData.debugMode) {
2066       if (!error) {
2067         fprintf(debugFP, "<ICS: ");
2068         show_bytes(debugFP, data, count);
2069         fprintf(debugFP, "\n");
2070       }
2071     }
2072
2073     if (appData.debugMode) { int f = forwardMostMove;
2074         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2075                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2076     }
2077     if (count > 0) {
2078         /* If last read ended with a partial line that we couldn't parse,
2079            prepend it to the new read and try again. */
2080         if (leftover_len > 0) {
2081             for (i=0; i<leftover_len; i++)
2082               buf[i] = buf[leftover_start + i];
2083         }
2084
2085         /* Copy in new characters, removing nulls and \r's */
2086         buf_len = leftover_len;
2087         for (i = 0; i < count; i++) {
2088             if (data[i] != NULLCHAR && data[i] != '\r')
2089               buf[buf_len++] = data[i];
2090             if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2091                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2092                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2093                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2094                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2095             }
2096         }
2097
2098         buf[buf_len] = NULLCHAR;
2099         next_out = leftover_len;
2100         leftover_start = 0;
2101         
2102         i = 0;
2103         while (i < buf_len) {
2104             /* Deal with part of the TELNET option negotiation
2105                protocol.  We refuse to do anything beyond the
2106                defaults, except that we allow the WILL ECHO option,
2107                which ICS uses to turn off password echoing when we are
2108                directly connected to it.  We reject this option
2109                if localLineEditing mode is on (always on in xboard)
2110                and we are talking to port 23, which might be a real
2111                telnet server that will try to keep WILL ECHO on permanently.
2112              */
2113             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2114                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2115                 unsigned char option;
2116                 oldi = i;
2117                 switch ((unsigned char) buf[++i]) {
2118                   case TN_WILL:
2119                     if (appData.debugMode)
2120                       fprintf(debugFP, "\n<WILL ");
2121                     switch (option = (unsigned char) buf[++i]) {
2122                       case TN_ECHO:
2123                         if (appData.debugMode)
2124                           fprintf(debugFP, "ECHO ");
2125                         /* Reply only if this is a change, according
2126                            to the protocol rules. */
2127                         if (remoteEchoOption) break;
2128                         if (appData.localLineEditing &&
2129                             atoi(appData.icsPort) == TN_PORT) {
2130                             TelnetRequest(TN_DONT, TN_ECHO);
2131                         } else {
2132                             EchoOff();
2133                             TelnetRequest(TN_DO, TN_ECHO);
2134                             remoteEchoOption = TRUE;
2135                         }
2136                         break;
2137                       default:
2138                         if (appData.debugMode)
2139                           fprintf(debugFP, "%d ", option);
2140                         /* Whatever this is, we don't want it. */
2141                         TelnetRequest(TN_DONT, option);
2142                         break;
2143                     }
2144                     break;
2145                   case TN_WONT:
2146                     if (appData.debugMode)
2147                       fprintf(debugFP, "\n<WONT ");
2148                     switch (option = (unsigned char) buf[++i]) {
2149                       case TN_ECHO:
2150                         if (appData.debugMode)
2151                           fprintf(debugFP, "ECHO ");
2152                         /* Reply only if this is a change, according
2153                            to the protocol rules. */
2154                         if (!remoteEchoOption) break;
2155                         EchoOn();
2156                         TelnetRequest(TN_DONT, TN_ECHO);
2157                         remoteEchoOption = FALSE;
2158                         break;
2159                       default:
2160                         if (appData.debugMode)
2161                           fprintf(debugFP, "%d ", (unsigned char) option);
2162                         /* Whatever this is, it must already be turned
2163                            off, because we never agree to turn on
2164                            anything non-default, so according to the
2165                            protocol rules, we don't reply. */
2166                         break;
2167                     }
2168                     break;
2169                   case TN_DO:
2170                     if (appData.debugMode)
2171                       fprintf(debugFP, "\n<DO ");
2172                     switch (option = (unsigned char) buf[++i]) {
2173                       default:
2174                         /* Whatever this is, we refuse to do it. */
2175                         if (appData.debugMode)
2176                           fprintf(debugFP, "%d ", option);
2177                         TelnetRequest(TN_WONT, option);
2178                         break;
2179                     }
2180                     break;
2181                   case TN_DONT:
2182                     if (appData.debugMode)
2183                       fprintf(debugFP, "\n<DONT ");
2184                     switch (option = (unsigned char) buf[++i]) {
2185                       default:
2186                         if (appData.debugMode)
2187                           fprintf(debugFP, "%d ", option);
2188                         /* Whatever this is, we are already not doing
2189                            it, because we never agree to do anything
2190                            non-default, so according to the protocol
2191                            rules, we don't reply. */
2192                         break;
2193                     }
2194                     break;
2195                   case TN_IAC:
2196                     if (appData.debugMode)
2197                       fprintf(debugFP, "\n<IAC ");
2198                     /* Doubled IAC; pass it through */
2199                     i--;
2200                     break;
2201                   default:
2202                     if (appData.debugMode)
2203                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2204                     /* Drop all other telnet commands on the floor */
2205                     break;
2206                 }
2207                 if (oldi > next_out)
2208                   SendToPlayer(&buf[next_out], oldi - next_out);
2209                 if (++i > next_out)
2210                   next_out = i;
2211                 continue;
2212             }
2213                 
2214             /* OK, this at least will *usually* work */
2215             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2216                 loggedOn = TRUE;
2217             }
2218             
2219             if (loggedOn && !intfSet) {
2220                 if (ics_type == ICS_ICC) {
2221                   sprintf(str,
2222                           "/set-quietly interface %s\n/set-quietly style 12\n",
2223                           programVersion);
2224           if (!appData.noJoin)
2225               strcat(str, "/set-quietly wrap 0\n");
2226                 } else if (ics_type == ICS_CHESSNET) {
2227                   sprintf(str, "/style 12\n");
2228                 } else {
2229                   strcpy(str, "alias $ @\n$set interface ");
2230                   strcat(str, programVersion);
2231                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2232 #ifdef WIN32
2233                   strcat(str, "$iset nohighlight 1\n");
2234 #endif
2235           if (!appData.noJoin)
2236               strcat(str, "$iset nowrap 1\n");
2237                   strcat(str, "$iset lock 1\n$style 12\n");
2238                 }
2239                 SendToICS(str);
2240                 NotifyFrontendLogin();
2241                 intfSet = TRUE;
2242             }
2243
2244             if (started == STARTED_COMMENT) {
2245                 /* Accumulate characters in comment */
2246                 parse[parse_pos++] = buf[i];
2247                 if (buf[i] == '\n') {
2248                     parse[parse_pos] = NULLCHAR;
2249                     if(chattingPartner>=0) {
2250                         char mess[MSG_SIZ];
2251                         sprintf(mess, "%s%s", talker, parse);
2252                         OutputChatMessage(chattingPartner, mess);
2253                         chattingPartner = -1;
2254                     } else
2255                     if(!suppressKibitz) // [HGM] kibitz
2256                         AppendComment(forwardMostMove, StripHighlight(parse));
2257                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2258                         int nrDigit = 0, nrAlph = 0, i;
2259                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2260                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2261                         parse[parse_pos] = NULLCHAR;
2262                         // try to be smart: if it does not look like search info, it should go to
2263                         // ICS interaction window after all, not to engine-output window.
2264                         for(i=0; i<parse_pos; i++) { // count letters and digits
2265                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2266                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2267                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2268                         }
2269                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2270                             int depth=0; float score;
2271                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2272                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2273                                 pvInfoList[forwardMostMove-1].depth = depth;
2274                                 pvInfoList[forwardMostMove-1].score = 100*score;
2275                             }
2276                             OutputKibitz(suppressKibitz, parse);
2277                         } else {
2278                             char tmp[MSG_SIZ];
2279                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2280                             SendToPlayer(tmp, strlen(tmp));
2281                         }
2282                     }
2283                     started = STARTED_NONE;
2284                 } else {
2285                     /* Don't match patterns against characters in chatter */
2286                     i++;
2287                     continue;
2288                 }
2289             }
2290             if (started == STARTED_CHATTER) {
2291                 if (buf[i] != '\n') {
2292                     /* Don't match patterns against characters in chatter */
2293                     i++;
2294                     continue;
2295                 }
2296                 started = STARTED_NONE;
2297             }
2298
2299             /* Kludge to deal with rcmd protocol */
2300             if (firstTime && looking_at(buf, &i, "\001*")) {
2301                 DisplayFatalError(&buf[1], 0, 1);
2302                 continue;
2303             } else {
2304                 firstTime = FALSE;
2305             }
2306
2307             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2308                 ics_type = ICS_ICC;
2309                 ics_prefix = "/";
2310                 if (appData.debugMode)
2311                   fprintf(debugFP, "ics_type %d\n", ics_type);
2312                 continue;
2313             }
2314             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2315                 ics_type = ICS_FICS;
2316                 ics_prefix = "$";
2317                 if (appData.debugMode)
2318                   fprintf(debugFP, "ics_type %d\n", ics_type);
2319                 continue;
2320             }
2321             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2322                 ics_type = ICS_CHESSNET;
2323                 ics_prefix = "/";
2324                 if (appData.debugMode)
2325                   fprintf(debugFP, "ics_type %d\n", ics_type);
2326                 continue;
2327             }
2328
2329             if (!loggedOn &&
2330                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2331                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2332                  looking_at(buf, &i, "will be \"*\""))) {
2333               strcpy(ics_handle, star_match[0]);
2334               continue;
2335             }
2336
2337             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2338               char buf[MSG_SIZ];
2339               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2340               DisplayIcsInteractionTitle(buf);
2341               have_set_title = TRUE;
2342             }
2343
2344             /* skip finger notes */
2345             if (started == STARTED_NONE &&
2346                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2347                  (buf[i] == '1' && buf[i+1] == '0')) &&
2348                 buf[i+2] == ':' && buf[i+3] == ' ') {
2349               started = STARTED_CHATTER;
2350               i += 3;
2351               continue;
2352             }
2353
2354             /* skip formula vars */
2355             if (started == STARTED_NONE &&
2356                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2357               started = STARTED_CHATTER;
2358               i += 3;
2359               continue;
2360             }
2361
2362             oldi = i;
2363             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2364             if (appData.autoKibitz && started == STARTED_NONE && 
2365                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2366                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2367                 if(looking_at(buf, &i, "* kibitzes: ") &&
2368                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2369                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2370                         suppressKibitz = TRUE;
2371                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2372                                 && (gameMode == IcsPlayingWhite)) ||
2373                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2374                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2375                             started = STARTED_CHATTER; // own kibitz we simply discard
2376                         else {
2377                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2378                             parse_pos = 0; parse[0] = NULLCHAR;
2379                             savingComment = TRUE;
2380                             suppressKibitz = gameMode != IcsObserving ? 2 :
2381                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2382                         } 
2383                         continue;
2384                 } else
2385                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2386                     started = STARTED_CHATTER;
2387                     suppressKibitz = TRUE;
2388                 }
2389             } // [HGM] kibitz: end of patch
2390
2391 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2392
2393             // [HGM] chat: intercept tells by users for which we have an open chat window
2394             channel = -1;
2395             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2396                                            looking_at(buf, &i, "* whispers:") ||
2397                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2398                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2399                 int p;
2400                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2401                 chattingPartner = -1;
2402
2403                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2404                 for(p=0; p<MAX_CHAT; p++) {
2405                     if(channel == atoi(chatPartner[p])) {
2406                     talker[0] = '['; strcat(talker, "]");
2407                     chattingPartner = p; break;
2408                     }
2409                 } else
2410                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2411                 for(p=0; p<MAX_CHAT; p++) {
2412                     if(!strcmp("WHISPER", chatPartner[p])) {
2413                         talker[0] = '['; strcat(talker, "]");
2414                         chattingPartner = p; break;
2415                     }
2416                 }
2417                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2418                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2419                     talker[0] = 0;
2420                     chattingPartner = p; break;
2421                 }
2422                 if(chattingPartner<0) i = oldi; else {
2423                     started = STARTED_COMMENT;
2424                     parse_pos = 0; parse[0] = NULLCHAR;
2425                     savingComment = TRUE;
2426                     suppressKibitz = TRUE;
2427                 }
2428             } // [HGM] chat: end of patch
2429
2430             if (appData.zippyTalk || appData.zippyPlay) {
2431                 /* [DM] Backup address for color zippy lines */
2432                 backup = i;
2433 #if ZIPPY
2434        #ifdef WIN32
2435                if (loggedOn == TRUE)
2436                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2437                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2438        #else
2439                 if (ZippyControl(buf, &i) ||
2440                     ZippyConverse(buf, &i) ||
2441                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2442                       loggedOn = TRUE;
2443                       if (!appData.colorize) continue;
2444                 }
2445        #endif
2446 #endif
2447             } // [DM] 'else { ' deleted
2448                 if (
2449                     /* Regular tells and says */
2450                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2451                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2452                     looking_at(buf, &i, "* says: ") ||
2453                     /* Don't color "message" or "messages" output */
2454                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2455                     looking_at(buf, &i, "*. * at *:*: ") ||
2456                     looking_at(buf, &i, "--* (*:*): ") ||
2457                     /* Message notifications (same color as tells) */
2458                     looking_at(buf, &i, "* has left a message ") ||
2459                     looking_at(buf, &i, "* just sent you a message:\n") ||
2460                     /* Whispers and kibitzes */
2461                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2462                     looking_at(buf, &i, "* kibitzes: ") ||
2463                     /* Channel tells */
2464                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2465
2466                   if (tkind == 1 && strchr(star_match[0], ':')) {
2467                       /* Avoid "tells you:" spoofs in channels */
2468                      tkind = 3;
2469                   }
2470                   if (star_match[0][0] == NULLCHAR ||
2471                       strchr(star_match[0], ' ') ||
2472                       (tkind == 3 && strchr(star_match[1], ' '))) {
2473                     /* Reject bogus matches */
2474                     i = oldi;
2475                   } else {
2476                     if (appData.colorize) {
2477                       if (oldi > next_out) {
2478                         SendToPlayer(&buf[next_out], oldi - next_out);
2479                         next_out = oldi;
2480                       }
2481                       switch (tkind) {
2482                       case 1:
2483                         Colorize(ColorTell, FALSE);
2484                         curColor = ColorTell;
2485                         break;
2486                       case 2:
2487                         Colorize(ColorKibitz, FALSE);
2488                         curColor = ColorKibitz;
2489                         break;
2490                       case 3:
2491                         p = strrchr(star_match[1], '(');
2492                         if (p == NULL) {
2493                           p = star_match[1];
2494                         } else {
2495                           p++;
2496                         }
2497                         if (atoi(p) == 1) {
2498                           Colorize(ColorChannel1, FALSE);
2499                           curColor = ColorChannel1;
2500                         } else {
2501                           Colorize(ColorChannel, FALSE);
2502                           curColor = ColorChannel;
2503                         }
2504                         break;
2505                       case 5:
2506                         curColor = ColorNormal;
2507                         break;
2508                       }
2509                     }
2510                     if (started == STARTED_NONE && appData.autoComment &&
2511                         (gameMode == IcsObserving ||
2512                          gameMode == IcsPlayingWhite ||
2513                          gameMode == IcsPlayingBlack)) {
2514                       parse_pos = i - oldi;
2515                       memcpy(parse, &buf[oldi], parse_pos);
2516                       parse[parse_pos] = NULLCHAR;
2517                       started = STARTED_COMMENT;
2518                       savingComment = TRUE;
2519                     } else {
2520                       started = STARTED_CHATTER;
2521                       savingComment = FALSE;
2522                     }
2523                     loggedOn = TRUE;
2524                     continue;
2525                   }
2526                 }
2527
2528                 if (looking_at(buf, &i, "* s-shouts: ") ||
2529                     looking_at(buf, &i, "* c-shouts: ")) {
2530                     if (appData.colorize) {
2531                         if (oldi > next_out) {
2532                             SendToPlayer(&buf[next_out], oldi - next_out);
2533                             next_out = oldi;
2534                         }
2535                         Colorize(ColorSShout, FALSE);
2536                         curColor = ColorSShout;
2537                     }
2538                     loggedOn = TRUE;
2539                     started = STARTED_CHATTER;
2540                     continue;
2541                 }
2542
2543                 if (looking_at(buf, &i, "--->")) {
2544                     loggedOn = TRUE;
2545                     continue;
2546                 }
2547
2548                 if (looking_at(buf, &i, "* shouts: ") ||
2549                     looking_at(buf, &i, "--> ")) {
2550                     if (appData.colorize) {
2551                         if (oldi > next_out) {
2552                             SendToPlayer(&buf[next_out], oldi - next_out);
2553                             next_out = oldi;
2554                         }
2555                         Colorize(ColorShout, FALSE);
2556                         curColor = ColorShout;
2557                     }
2558                     loggedOn = TRUE;
2559                     started = STARTED_CHATTER;
2560                     continue;
2561                 }
2562
2563                 if (looking_at( buf, &i, "Challenge:")) {
2564                     if (appData.colorize) {
2565                         if (oldi > next_out) {
2566                             SendToPlayer(&buf[next_out], oldi - next_out);
2567                             next_out = oldi;
2568                         }
2569                         Colorize(ColorChallenge, FALSE);
2570                         curColor = ColorChallenge;
2571                     }
2572                     loggedOn = TRUE;
2573                     continue;
2574                 }
2575
2576                 if (looking_at(buf, &i, "* offers you") ||
2577                     looking_at(buf, &i, "* offers to be") ||
2578                     looking_at(buf, &i, "* would like to") ||
2579                     looking_at(buf, &i, "* requests to") ||
2580                     looking_at(buf, &i, "Your opponent offers") ||
2581                     looking_at(buf, &i, "Your opponent requests")) {
2582
2583                     if (appData.colorize) {
2584                         if (oldi > next_out) {
2585                             SendToPlayer(&buf[next_out], oldi - next_out);
2586                             next_out = oldi;
2587                         }
2588                         Colorize(ColorRequest, FALSE);
2589                         curColor = ColorRequest;
2590                     }
2591                     continue;
2592                 }
2593
2594                 if (looking_at(buf, &i, "* (*) seeking")) {
2595                     if (appData.colorize) {
2596                         if (oldi > next_out) {
2597                             SendToPlayer(&buf[next_out], oldi - next_out);
2598                             next_out = oldi;
2599                         }
2600                         Colorize(ColorSeek, FALSE);
2601                         curColor = ColorSeek;
2602                     }
2603                     continue;
2604             }
2605
2606             if (looking_at(buf, &i, "\\   ")) {
2607                 if (prevColor != ColorNormal) {
2608                     if (oldi > next_out) {
2609                         SendToPlayer(&buf[next_out], oldi - next_out);
2610                         next_out = oldi;
2611                     }
2612                     Colorize(prevColor, TRUE);
2613                     curColor = prevColor;
2614                 }
2615                 if (savingComment) {
2616                     parse_pos = i - oldi;
2617                     memcpy(parse, &buf[oldi], parse_pos);
2618                     parse[parse_pos] = NULLCHAR;
2619                     started = STARTED_COMMENT;
2620                 } else {
2621                     started = STARTED_CHATTER;
2622                 }
2623                 continue;
2624             }
2625
2626             if (looking_at(buf, &i, "Black Strength :") ||
2627                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2628                 looking_at(buf, &i, "<10>") ||
2629                 looking_at(buf, &i, "#@#")) {
2630                 /* Wrong board style */
2631                 loggedOn = TRUE;
2632                 SendToICS(ics_prefix);
2633                 SendToICS("set style 12\n");
2634                 SendToICS(ics_prefix);
2635                 SendToICS("refresh\n");
2636                 continue;
2637             }
2638             
2639             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2640                 ICSInitScript();
2641                 have_sent_ICS_logon = 1;
2642                 continue;
2643             }
2644               
2645             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2646                 (looking_at(buf, &i, "\n<12> ") ||
2647                  looking_at(buf, &i, "<12> "))) {
2648                 loggedOn = TRUE;
2649                 if (oldi > next_out) {
2650                     SendToPlayer(&buf[next_out], oldi - next_out);
2651                 }
2652                 next_out = i;
2653                 started = STARTED_BOARD;
2654                 parse_pos = 0;
2655                 continue;
2656             }
2657
2658             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2659                 looking_at(buf, &i, "<b1> ")) {
2660                 if (oldi > next_out) {
2661                     SendToPlayer(&buf[next_out], oldi - next_out);
2662                 }
2663                 next_out = i;
2664                 started = STARTED_HOLDINGS;
2665                 parse_pos = 0;
2666                 continue;
2667             }
2668
2669             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2670                 loggedOn = TRUE;
2671                 /* Header for a move list -- first line */
2672
2673                 switch (ics_getting_history) {
2674                   case H_FALSE:
2675                     switch (gameMode) {
2676                       case IcsIdle:
2677                       case BeginningOfGame:
2678                         /* User typed "moves" or "oldmoves" while we
2679                            were idle.  Pretend we asked for these
2680                            moves and soak them up so user can step
2681                            through them and/or save them.
2682                            */
2683                         Reset(FALSE, TRUE);
2684                         gameMode = IcsObserving;
2685                         ModeHighlight();
2686                         ics_gamenum = -1;
2687                         ics_getting_history = H_GOT_UNREQ_HEADER;
2688                         break;
2689                       case EditGame: /*?*/
2690                       case EditPosition: /*?*/
2691                         /* Should above feature work in these modes too? */
2692                         /* For now it doesn't */
2693                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2694                         break;
2695                       default:
2696                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2697                         break;
2698                     }
2699                     break;
2700                   case H_REQUESTED:
2701                     /* Is this the right one? */
2702                     if (gameInfo.white && gameInfo.black &&
2703                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2704                         strcmp(gameInfo.black, star_match[2]) == 0) {
2705                         /* All is well */
2706                         ics_getting_history = H_GOT_REQ_HEADER;
2707                     }
2708                     break;
2709                   case H_GOT_REQ_HEADER:
2710                   case H_GOT_UNREQ_HEADER:
2711                   case H_GOT_UNWANTED_HEADER:
2712                   case H_GETTING_MOVES:
2713                     /* Should not happen */
2714                     DisplayError(_("Error gathering move list: two headers"), 0);
2715                     ics_getting_history = H_FALSE;
2716                     break;
2717                 }
2718
2719                 /* Save player ratings into gameInfo if needed */
2720                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2721                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2722                     (gameInfo.whiteRating == -1 ||
2723                      gameInfo.blackRating == -1)) {
2724
2725                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2726                     gameInfo.blackRating = string_to_rating(star_match[3]);
2727                     if (appData.debugMode)
2728                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2729                               gameInfo.whiteRating, gameInfo.blackRating);
2730                 }
2731                 continue;
2732             }
2733
2734             if (looking_at(buf, &i,
2735               "* * match, initial time: * minute*, increment: * second")) {
2736                 /* Header for a move list -- second line */
2737                 /* Initial board will follow if this is a wild game */
2738                 if (gameInfo.event != NULL) free(gameInfo.event);
2739                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2740                 gameInfo.event = StrSave(str);
2741                 /* [HGM] we switched variant. Translate boards if needed. */
2742                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2743                 continue;
2744             }
2745
2746             if (looking_at(buf, &i, "Move  ")) {
2747                 /* Beginning of a move list */
2748                 switch (ics_getting_history) {
2749                   case H_FALSE:
2750                     /* Normally should not happen */
2751                     /* Maybe user hit reset while we were parsing */
2752                     break;
2753                   case H_REQUESTED:
2754                     /* Happens if we are ignoring a move list that is not
2755                      * the one we just requested.  Common if the user
2756                      * tries to observe two games without turning off
2757                      * getMoveList */
2758                     break;
2759                   case H_GETTING_MOVES:
2760                     /* Should not happen */
2761                     DisplayError(_("Error gathering move list: nested"), 0);
2762                     ics_getting_history = H_FALSE;
2763                     break;
2764                   case H_GOT_REQ_HEADER:
2765                     ics_getting_history = H_GETTING_MOVES;
2766                     started = STARTED_MOVES;
2767                     parse_pos = 0;
2768                     if (oldi > next_out) {
2769                         SendToPlayer(&buf[next_out], oldi - next_out);
2770                     }
2771                     break;
2772                   case H_GOT_UNREQ_HEADER:
2773                     ics_getting_history = H_GETTING_MOVES;
2774                     started = STARTED_MOVES_NOHIDE;
2775                     parse_pos = 0;
2776                     break;
2777                   case H_GOT_UNWANTED_HEADER:
2778                     ics_getting_history = H_FALSE;
2779                     break;
2780                 }
2781                 continue;
2782             }                           
2783             
2784             if (looking_at(buf, &i, "% ") ||
2785                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2786                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2787                 savingComment = FALSE;
2788                 switch (started) {
2789                   case STARTED_MOVES:
2790                   case STARTED_MOVES_NOHIDE:
2791                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2792                     parse[parse_pos + i - oldi] = NULLCHAR;
2793                     ParseGameHistory(parse);
2794 #if ZIPPY
2795                     if (appData.zippyPlay && first.initDone) {
2796                         FeedMovesToProgram(&first, forwardMostMove);
2797                         if (gameMode == IcsPlayingWhite) {
2798                             if (WhiteOnMove(forwardMostMove)) {
2799                                 if (first.sendTime) {
2800                                   if (first.useColors) {
2801                                     SendToProgram("black\n", &first); 
2802                                   }
2803                                   SendTimeRemaining(&first, TRUE);
2804                                 }
2805                                 if (first.useColors) {
2806                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2807                                 }
2808                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2809                                 first.maybeThinking = TRUE;
2810                             } else {
2811                                 if (first.usePlayother) {
2812                                   if (first.sendTime) {
2813                                     SendTimeRemaining(&first, TRUE);
2814                                   }
2815                                   SendToProgram("playother\n", &first);
2816                                   firstMove = FALSE;
2817                                 } else {
2818                                   firstMove = TRUE;
2819                                 }
2820                             }
2821                         } else if (gameMode == IcsPlayingBlack) {
2822                             if (!WhiteOnMove(forwardMostMove)) {
2823                                 if (first.sendTime) {
2824                                   if (first.useColors) {
2825                                     SendToProgram("white\n", &first);
2826                                   }
2827                                   SendTimeRemaining(&first, FALSE);
2828                                 }
2829                                 if (first.useColors) {
2830                                   SendToProgram("black\n", &first);
2831                                 }
2832                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2833                                 first.maybeThinking = TRUE;
2834                             } else {
2835                                 if (first.usePlayother) {
2836                                   if (first.sendTime) {
2837                                     SendTimeRemaining(&first, FALSE);
2838                                   }
2839                                   SendToProgram("playother\n", &first);
2840                                   firstMove = FALSE;
2841                                 } else {
2842                                   firstMove = TRUE;
2843                                 }
2844                             }
2845                         }                       
2846                     }
2847 #endif
2848                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2849                         /* Moves came from oldmoves or moves command
2850                            while we weren't doing anything else.
2851                            */
2852                         currentMove = forwardMostMove;
2853                         ClearHighlights();/*!!could figure this out*/
2854                         flipView = appData.flipView;
2855                         DrawPosition(FALSE, boards[currentMove]);
2856                         DisplayBothClocks();
2857                         sprintf(str, "%s vs. %s",
2858                                 gameInfo.white, gameInfo.black);
2859                         DisplayTitle(str);
2860                         gameMode = IcsIdle;
2861                     } else {
2862                         /* Moves were history of an active game */
2863                         if (gameInfo.resultDetails != NULL) {
2864                             free(gameInfo.resultDetails);
2865                             gameInfo.resultDetails = NULL;
2866                         }
2867                     }
2868                     HistorySet(parseList, backwardMostMove,
2869                                forwardMostMove, currentMove-1);
2870                     DisplayMove(currentMove - 1);
2871                     if (started == STARTED_MOVES) next_out = i;
2872                     started = STARTED_NONE;
2873                     ics_getting_history = H_FALSE;
2874                     break;
2875
2876                   case STARTED_OBSERVE:
2877                     started = STARTED_NONE;
2878                     SendToICS(ics_prefix);
2879                     SendToICS("refresh\n");
2880                     break;
2881
2882                   default:
2883                     break;
2884                 }
2885                 if(bookHit) { // [HGM] book: simulate book reply
2886                     static char bookMove[MSG_SIZ]; // a bit generous?
2887
2888                     programStats.nodes = programStats.depth = programStats.time = 
2889                     programStats.score = programStats.got_only_move = 0;
2890                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2891
2892                     strcpy(bookMove, "move ");
2893                     strcat(bookMove, bookHit);
2894                     HandleMachineMove(bookMove, &first);
2895                 }
2896                 continue;
2897             }
2898             
2899             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2900                  started == STARTED_HOLDINGS ||
2901                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2902                 /* Accumulate characters in move list or board */
2903                 parse[parse_pos++] = buf[i];
2904             }
2905             
2906             /* Start of game messages.  Mostly we detect start of game
2907                when the first board image arrives.  On some versions
2908                of the ICS, though, we need to do a "refresh" after starting
2909                to observe in order to get the current board right away. */
2910             if (looking_at(buf, &i, "Adding game * to observation list")) {
2911                 started = STARTED_OBSERVE;
2912                 continue;
2913             }
2914
2915             /* Handle auto-observe */
2916             if (appData.autoObserve &&
2917                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2918                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2919                 char *player;
2920                 /* Choose the player that was highlighted, if any. */
2921                 if (star_match[0][0] == '\033' ||
2922                     star_match[1][0] != '\033') {
2923                     player = star_match[0];
2924                 } else {
2925                     player = star_match[2];
2926                 }
2927                 sprintf(str, "%sobserve %s\n",
2928                         ics_prefix, StripHighlightAndTitle(player));
2929                 SendToICS(str);
2930
2931                 /* Save ratings from notify string */
2932                 strcpy(player1Name, star_match[0]);
2933                 player1Rating = string_to_rating(star_match[1]);
2934                 strcpy(player2Name, star_match[2]);
2935                 player2Rating = string_to_rating(star_match[3]);
2936
2937                 if (appData.debugMode)
2938                   fprintf(debugFP, 
2939                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2940                           player1Name, player1Rating,
2941                           player2Name, player2Rating);
2942
2943                 continue;
2944             }
2945
2946             /* Deal with automatic examine mode after a game,
2947                and with IcsObserving -> IcsExamining transition */
2948             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2949                 looking_at(buf, &i, "has made you an examiner of game *")) {
2950
2951                 int gamenum = atoi(star_match[0]);
2952                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2953                     gamenum == ics_gamenum) {
2954                     /* We were already playing or observing this game;
2955                        no need to refetch history */
2956                     gameMode = IcsExamining;
2957                     if (pausing) {
2958                         pauseExamForwardMostMove = forwardMostMove;
2959                     } else if (currentMove < forwardMostMove) {
2960                         ForwardInner(forwardMostMove);
2961                     }
2962                 } else {
2963                     /* I don't think this case really can happen */
2964                     SendToICS(ics_prefix);
2965                     SendToICS("refresh\n");
2966                 }
2967                 continue;
2968             }    
2969             
2970             /* Error messages */
2971 //          if (ics_user_moved) {
2972             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2973                 if (looking_at(buf, &i, "Illegal move") ||
2974                     looking_at(buf, &i, "Not a legal move") ||
2975                     looking_at(buf, &i, "Your king is in check") ||
2976                     looking_at(buf, &i, "It isn't your turn") ||
2977                     looking_at(buf, &i, "It is not your move")) {
2978                     /* Illegal move */
2979                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2980                         currentMove = --forwardMostMove;
2981                         DisplayMove(currentMove - 1); /* before DMError */
2982                         DrawPosition(FALSE, boards[currentMove]);
2983                         SwitchClocks();
2984                         DisplayBothClocks();
2985                     }
2986                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2987                     ics_user_moved = 0;
2988                     continue;
2989                 }
2990             }
2991
2992             if (looking_at(buf, &i, "still have time") ||
2993                 looking_at(buf, &i, "not out of time") ||
2994                 looking_at(buf, &i, "either player is out of time") ||
2995                 looking_at(buf, &i, "has timeseal; checking")) {
2996                 /* We must have called his flag a little too soon */
2997                 whiteFlag = blackFlag = FALSE;
2998                 continue;
2999             }
3000
3001             if (looking_at(buf, &i, "added * seconds to") ||
3002                 looking_at(buf, &i, "seconds were added to")) {
3003                 /* Update the clocks */
3004                 SendToICS(ics_prefix);
3005                 SendToICS("refresh\n");
3006                 continue;
3007             }
3008
3009             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3010                 ics_clock_paused = TRUE;
3011                 StopClocks();
3012                 continue;
3013             }
3014
3015             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3016                 ics_clock_paused = FALSE;
3017                 StartClocks();
3018                 continue;
3019             }
3020
3021             /* Grab player ratings from the Creating: message.
3022                Note we have to check for the special case when
3023                the ICS inserts things like [white] or [black]. */
3024             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3025                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3026                 /* star_matches:
3027                    0    player 1 name (not necessarily white)
3028                    1    player 1 rating
3029                    2    empty, white, or black (IGNORED)
3030                    3    player 2 name (not necessarily black)
3031                    4    player 2 rating
3032                    
3033                    The names/ratings are sorted out when the game
3034                    actually starts (below).
3035                 */
3036                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3037                 player1Rating = string_to_rating(star_match[1]);
3038                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3039                 player2Rating = string_to_rating(star_match[4]);
3040
3041                 if (appData.debugMode)
3042                   fprintf(debugFP, 
3043                           "Ratings from 'Creating:' %s %d, %s %d\n",
3044                           player1Name, player1Rating,
3045                           player2Name, player2Rating);
3046
3047                 continue;
3048             }
3049             
3050             /* Improved generic start/end-of-game messages */
3051             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3052                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3053                 /* If tkind == 0: */
3054                 /* star_match[0] is the game number */
3055                 /*           [1] is the white player's name */
3056                 /*           [2] is the black player's name */
3057                 /* For end-of-game: */
3058                 /*           [3] is the reason for the game end */
3059                 /*           [4] is a PGN end game-token, preceded by " " */
3060                 /* For start-of-game: */
3061                 /*           [3] begins with "Creating" or "Continuing" */
3062                 /*           [4] is " *" or empty (don't care). */
3063                 int gamenum = atoi(star_match[0]);
3064                 char *whitename, *blackname, *why, *endtoken;
3065                 ChessMove endtype = (ChessMove) 0;
3066
3067                 if (tkind == 0) {
3068                   whitename = star_match[1];
3069                   blackname = star_match[2];
3070                   why = star_match[3];
3071                   endtoken = star_match[4];
3072                 } else {
3073                   whitename = star_match[1];
3074                   blackname = star_match[3];
3075                   why = star_match[5];
3076                   endtoken = star_match[6];
3077                 }
3078
3079                 /* Game start messages */
3080                 if (strncmp(why, "Creating ", 9) == 0 ||
3081                     strncmp(why, "Continuing ", 11) == 0) {
3082                     gs_gamenum = gamenum;
3083                     strcpy(gs_kind, strchr(why, ' ') + 1);
3084 #if ZIPPY
3085                     if (appData.zippyPlay) {
3086                         ZippyGameStart(whitename, blackname);
3087                     }
3088 #endif /*ZIPPY*/
3089                     continue;
3090                 }
3091
3092                 /* Game end messages */
3093                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3094                     ics_gamenum != gamenum) {
3095                     continue;
3096                 }
3097                 while (endtoken[0] == ' ') endtoken++;
3098                 switch (endtoken[0]) {
3099                   case '*':
3100                   default:
3101                     endtype = GameUnfinished;
3102                     break;
3103                   case '0':
3104                     endtype = BlackWins;
3105                     break;
3106                   case '1':
3107                     if (endtoken[1] == '/')
3108                       endtype = GameIsDrawn;
3109                     else
3110                       endtype = WhiteWins;
3111                     break;
3112                 }
3113                 GameEnds(endtype, why, GE_ICS);
3114 #if ZIPPY
3115                 if (appData.zippyPlay && first.initDone) {
3116                     ZippyGameEnd(endtype, why);
3117                     if (first.pr == NULL) {
3118                       /* Start the next process early so that we'll
3119                          be ready for the next challenge */
3120                       StartChessProgram(&first);
3121                     }
3122                     /* Send "new" early, in case this command takes
3123                        a long time to finish, so that we'll be ready
3124                        for the next challenge. */
3125                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3126                     Reset(TRUE, TRUE);
3127                 }
3128 #endif /*ZIPPY*/
3129                 continue;
3130             }
3131
3132             if (looking_at(buf, &i, "Removing game * from observation") ||
3133                 looking_at(buf, &i, "no longer observing game *") ||
3134                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3135                 if (gameMode == IcsObserving &&
3136                     atoi(star_match[0]) == ics_gamenum)
3137                   {
3138                       /* icsEngineAnalyze */
3139                       if (appData.icsEngineAnalyze) {
3140                             ExitAnalyzeMode();
3141                             ModeHighlight();
3142                       }
3143                       StopClocks();
3144                       gameMode = IcsIdle;
3145                       ics_gamenum = -1;
3146                       ics_user_moved = FALSE;
3147                   }
3148                 continue;
3149             }
3150
3151             if (looking_at(buf, &i, "no longer examining game *")) {
3152                 if (gameMode == IcsExamining &&
3153                     atoi(star_match[0]) == ics_gamenum)
3154                   {
3155                       gameMode = IcsIdle;
3156                       ics_gamenum = -1;
3157                       ics_user_moved = FALSE;
3158                   }
3159                 continue;
3160             }
3161
3162             /* Advance leftover_start past any newlines we find,
3163                so only partial lines can get reparsed */
3164             if (looking_at(buf, &i, "\n")) {
3165                 prevColor = curColor;
3166                 if (curColor != ColorNormal) {
3167                     if (oldi > next_out) {
3168                         SendToPlayer(&buf[next_out], oldi - next_out);
3169                         next_out = oldi;
3170                     }
3171                     Colorize(ColorNormal, FALSE);
3172                     curColor = ColorNormal;
3173                 }
3174                 if (started == STARTED_BOARD) {
3175                     started = STARTED_NONE;
3176                     parse[parse_pos] = NULLCHAR;
3177                     ParseBoard12(parse);
3178                     ics_user_moved = 0;
3179
3180                     /* Send premove here */
3181                     if (appData.premove) {
3182                       char str[MSG_SIZ];
3183                       if (currentMove == 0 &&
3184                           gameMode == IcsPlayingWhite &&
3185                           appData.premoveWhite) {
3186                         sprintf(str, "%s%s\n", ics_prefix,
3187                                 appData.premoveWhiteText);
3188                         if (appData.debugMode)
3189                           fprintf(debugFP, "Sending premove:\n");
3190                         SendToICS(str);
3191                       } else if (currentMove == 1 &&
3192                                  gameMode == IcsPlayingBlack &&
3193                                  appData.premoveBlack) {
3194                         sprintf(str, "%s%s\n", ics_prefix,
3195                                 appData.premoveBlackText);
3196                         if (appData.debugMode)
3197                           fprintf(debugFP, "Sending premove:\n");
3198                         SendToICS(str);
3199                       } else if (gotPremove) {
3200                         gotPremove = 0;
3201                         ClearPremoveHighlights();
3202                         if (appData.debugMode)
3203                           fprintf(debugFP, "Sending premove:\n");
3204                           UserMoveEvent(premoveFromX, premoveFromY, 
3205                                         premoveToX, premoveToY, 
3206                                         premovePromoChar);
3207                       }
3208                     }
3209
3210                     /* Usually suppress following prompt */
3211                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3212                         if (looking_at(buf, &i, "*% ")) {
3213                             savingComment = FALSE;
3214                         }
3215                     }
3216                     next_out = i;
3217                 } else if (started == STARTED_HOLDINGS) {
3218                     int gamenum;
3219                     char new_piece[MSG_SIZ];
3220                     started = STARTED_NONE;
3221                     parse[parse_pos] = NULLCHAR;
3222                     if (appData.debugMode)
3223                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3224                                                         parse, currentMove);
3225                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3226                         gamenum == ics_gamenum) {
3227                         if (gameInfo.variant == VariantNormal) {
3228                           /* [HGM] We seem to switch variant during a game!
3229                            * Presumably no holdings were displayed, so we have
3230                            * to move the position two files to the right to
3231                            * create room for them!
3232                            */
3233                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3234                           /* Get a move list just to see the header, which
3235                              will tell us whether this is really bug or zh */
3236                           if (ics_getting_history == H_FALSE) {
3237                             ics_getting_history = H_REQUESTED;
3238                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3239                             SendToICS(str);
3240                           }
3241                         }
3242                         new_piece[0] = NULLCHAR;
3243                         sscanf(parse, "game %d white [%s black [%s <- %s",
3244                                &gamenum, white_holding, black_holding,
3245                                new_piece);
3246                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3247                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3248                         /* [HGM] copy holdings to board holdings area */
3249                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3250                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3251 #if ZIPPY
3252                         if (appData.zippyPlay && first.initDone) {
3253                             ZippyHoldings(white_holding, black_holding,
3254                                           new_piece);
3255                         }
3256 #endif /*ZIPPY*/
3257                         if (tinyLayout || smallLayout) {
3258                             char wh[16], bh[16];
3259                             PackHolding(wh, white_holding);
3260                             PackHolding(bh, black_holding);
3261                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3262                                     gameInfo.white, gameInfo.black);
3263                         } else {
3264                             sprintf(str, "%s [%s] vs. %s [%s]",
3265                                     gameInfo.white, white_holding,
3266                                     gameInfo.black, black_holding);
3267                         }
3268
3269                         DrawPosition(FALSE, boards[currentMove]);
3270                         DisplayTitle(str);
3271                     }
3272                     /* Suppress following prompt */
3273                     if (looking_at(buf, &i, "*% ")) {
3274                         savingComment = FALSE;
3275                     }
3276                     next_out = i;
3277                 }
3278                 continue;
3279             }
3280
3281             i++;                /* skip unparsed character and loop back */
3282         }
3283         
3284         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3285             started != STARTED_HOLDINGS && i > next_out) {
3286             SendToPlayer(&buf[next_out], i - next_out);
3287             next_out = i;
3288         }
3289         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3290         
3291         leftover_len = buf_len - leftover_start;
3292         /* if buffer ends with something we couldn't parse,
3293            reparse it after appending the next read */
3294         
3295     } else if (count == 0) {
3296         RemoveInputSource(isr);
3297         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3298     } else {
3299         DisplayFatalError(_("Error reading from ICS"), error, 1);
3300     }
3301 }
3302
3303
3304 /* Board style 12 looks like this:
3305    
3306    <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
3307    
3308  * The "<12> " is stripped before it gets to this routine.  The two
3309  * trailing 0's (flip state and clock ticking) are later addition, and
3310  * some chess servers may not have them, or may have only the first.
3311  * Additional trailing fields may be added in the future.  
3312  */
3313
3314 #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"
3315
3316 #define RELATION_OBSERVING_PLAYED    0
3317 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3318 #define RELATION_PLAYING_MYMOVE      1
3319 #define RELATION_PLAYING_NOTMYMOVE  -1
3320 #define RELATION_EXAMINING           2
3321 #define RELATION_ISOLATED_BOARD     -3
3322 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3323
3324 void
3325 ParseBoard12(string)
3326      char *string;
3327
3328     GameMode newGameMode;
3329     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3330     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3331     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3332     char to_play, board_chars[200];
3333     char move_str[500], str[500], elapsed_time[500];
3334     char black[32], white[32];
3335     Board board;
3336     int prevMove = currentMove;
3337     int ticking = 2;
3338     ChessMove moveType;
3339     int fromX, fromY, toX, toY;
3340     char promoChar;
3341     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3342     char *bookHit = NULL; // [HGM] book
3343
3344     fromX = fromY = toX = toY = -1;
3345     
3346     newGame = FALSE;
3347
3348     if (appData.debugMode)
3349       fprintf(debugFP, _("Parsing board: %s\n"), string);
3350
3351     move_str[0] = NULLCHAR;
3352     elapsed_time[0] = NULLCHAR;
3353     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3354         int  i = 0, j;
3355         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3356             if(string[i] == ' ') { ranks++; files = 0; }
3357             else files++;
3358             i++;
3359         }
3360         for(j = 0; j <i; j++) board_chars[j] = string[j];
3361         board_chars[i] = '\0';
3362         string += i + 1;
3363     }
3364     n = sscanf(string, PATTERN, &to_play, &double_push,
3365                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3366                &gamenum, white, black, &relation, &basetime, &increment,
3367                &white_stren, &black_stren, &white_time, &black_time,
3368                &moveNum, str, elapsed_time, move_str, &ics_flip,
3369                &ticking);
3370
3371     if (n < 21) {
3372         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3373         DisplayError(str, 0);
3374         return;
3375     }
3376
3377     /* Convert the move number to internal form */
3378     moveNum = (moveNum - 1) * 2;
3379     if (to_play == 'B') moveNum++;
3380     if (moveNum >= MAX_MOVES) {
3381       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3382                         0, 1);
3383       return;
3384     }
3385     
3386     switch (relation) {
3387       case RELATION_OBSERVING_PLAYED:
3388       case RELATION_OBSERVING_STATIC:
3389         if (gamenum == -1) {
3390             /* Old ICC buglet */
3391             relation = RELATION_OBSERVING_STATIC;
3392         }
3393         newGameMode = IcsObserving;
3394         break;
3395       case RELATION_PLAYING_MYMOVE:
3396       case RELATION_PLAYING_NOTMYMOVE:
3397         newGameMode =
3398           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3399             IcsPlayingWhite : IcsPlayingBlack;
3400         break;
3401       case RELATION_EXAMINING:
3402         newGameMode = IcsExamining;
3403         break;
3404       case RELATION_ISOLATED_BOARD:
3405       default:
3406         /* Just display this board.  If user was doing something else,
3407            we will forget about it until the next board comes. */ 
3408         newGameMode = IcsIdle;
3409         break;
3410       case RELATION_STARTING_POSITION:
3411         newGameMode = gameMode;
3412         break;
3413     }
3414     
3415     /* Modify behavior for initial board display on move listing
3416        of wild games.
3417        */
3418     switch (ics_getting_history) {
3419       case H_FALSE:
3420       case H_REQUESTED:
3421         break;
3422       case H_GOT_REQ_HEADER:
3423       case H_GOT_UNREQ_HEADER:
3424         /* This is the initial position of the current game */
3425         gamenum = ics_gamenum;
3426         moveNum = 0;            /* old ICS bug workaround */
3427         if (to_play == 'B') {
3428           startedFromSetupPosition = TRUE;
3429           blackPlaysFirst = TRUE;
3430           moveNum = 1;
3431           if (forwardMostMove == 0) forwardMostMove = 1;
3432           if (backwardMostMove == 0) backwardMostMove = 1;
3433           if (currentMove == 0) currentMove = 1;
3434         }
3435         newGameMode = gameMode;
3436         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3437         break;
3438       case H_GOT_UNWANTED_HEADER:
3439         /* This is an initial board that we don't want */
3440         return;
3441       case H_GETTING_MOVES:
3442         /* Should not happen */
3443         DisplayError(_("Error gathering move list: extra board"), 0);
3444         ics_getting_history = H_FALSE;
3445         return;
3446     }
3447     
3448     /* Take action if this is the first board of a new game, or of a
3449        different game than is currently being displayed.  */
3450     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3451         relation == RELATION_ISOLATED_BOARD) {
3452         
3453         /* Forget the old game and get the history (if any) of the new one */
3454         if (gameMode != BeginningOfGame) {
3455           Reset(FALSE, TRUE);
3456         }
3457         newGame = TRUE;
3458         if (appData.autoRaiseBoard) BoardToTop();
3459         prevMove = -3;
3460         if (gamenum == -1) {
3461             newGameMode = IcsIdle;
3462         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3463                    appData.getMoveList) {
3464             /* Need to get game history */
3465             ics_getting_history = H_REQUESTED;
3466             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3467             SendToICS(str);
3468         }
3469         
3470         /* Initially flip the board to have black on the bottom if playing
3471            black or if the ICS flip flag is set, but let the user change
3472            it with the Flip View button. */
3473         flipView = appData.autoFlipView ? 
3474           (newGameMode == IcsPlayingBlack) || ics_flip :
3475           appData.flipView;
3476         
3477         /* Done with values from previous mode; copy in new ones */
3478         gameMode = newGameMode;
3479         ModeHighlight();
3480         ics_gamenum = gamenum;
3481         if (gamenum == gs_gamenum) {
3482             int klen = strlen(gs_kind);
3483             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3484             sprintf(str, "ICS %s", gs_kind);
3485             gameInfo.event = StrSave(str);
3486         } else {
3487             gameInfo.event = StrSave("ICS game");
3488         }
3489         gameInfo.site = StrSave(appData.icsHost);
3490         gameInfo.date = PGNDate();
3491         gameInfo.round = StrSave("-");
3492         gameInfo.white = StrSave(white);
3493         gameInfo.black = StrSave(black);
3494         timeControl = basetime * 60 * 1000;
3495         timeControl_2 = 0;
3496         timeIncrement = increment * 1000;
3497         movesPerSession = 0;
3498         gameInfo.timeControl = TimeControlTagValue();
3499         VariantSwitch(board, StringToVariant(gameInfo.event) );
3500   if (appData.debugMode) {
3501     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3502     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3503     setbuf(debugFP, NULL);
3504   }
3505
3506         gameInfo.outOfBook = NULL;
3507         
3508         /* Do we have the ratings? */
3509         if (strcmp(player1Name, white) == 0 &&
3510             strcmp(player2Name, black) == 0) {
3511             if (appData.debugMode)
3512               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3513                       player1Rating, player2Rating);
3514             gameInfo.whiteRating = player1Rating;
3515             gameInfo.blackRating = player2Rating;
3516         } else if (strcmp(player2Name, white) == 0 &&
3517                    strcmp(player1Name, black) == 0) {
3518             if (appData.debugMode)
3519               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3520                       player2Rating, player1Rating);
3521             gameInfo.whiteRating = player2Rating;
3522             gameInfo.blackRating = player1Rating;
3523         }
3524         player1Name[0] = player2Name[0] = NULLCHAR;
3525
3526         /* Silence shouts if requested */
3527         if (appData.quietPlay &&
3528             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3529             SendToICS(ics_prefix);
3530             SendToICS("set shout 0\n");
3531         }
3532     }
3533     
3534     /* Deal with midgame name changes */
3535     if (!newGame) {
3536         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3537             if (gameInfo.white) free(gameInfo.white);
3538             gameInfo.white = StrSave(white);
3539         }
3540         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3541             if (gameInfo.black) free(gameInfo.black);
3542             gameInfo.black = StrSave(black);
3543         }
3544     }
3545     
3546     /* Throw away game result if anything actually changes in examine mode */
3547     if (gameMode == IcsExamining && !newGame) {
3548         gameInfo.result = GameUnfinished;
3549         if (gameInfo.resultDetails != NULL) {
3550             free(gameInfo.resultDetails);
3551             gameInfo.resultDetails = NULL;
3552         }
3553     }
3554     
3555     /* In pausing && IcsExamining mode, we ignore boards coming
3556        in if they are in a different variation than we are. */
3557     if (pauseExamInvalid) return;
3558     if (pausing && gameMode == IcsExamining) {
3559         if (moveNum <= pauseExamForwardMostMove) {
3560             pauseExamInvalid = TRUE;
3561             forwardMostMove = pauseExamForwardMostMove;
3562             return;
3563         }
3564     }
3565     
3566   if (appData.debugMode) {
3567     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3568   }
3569     /* Parse the board */
3570     for (k = 0; k < ranks; k++) {
3571       for (j = 0; j < files; j++)
3572         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3573       if(gameInfo.holdingsWidth > 1) {
3574            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3575            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3576       }
3577     }
3578     CopyBoard(boards[moveNum], board);
3579     if (moveNum == 0) {
3580         startedFromSetupPosition =
3581           !CompareBoards(board, initialPosition);
3582         if(startedFromSetupPosition)
3583             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3584     }
3585
3586     /* [HGM] Set castling rights. Take the outermost Rooks,
3587        to make it also work for FRC opening positions. Note that board12
3588        is really defective for later FRC positions, as it has no way to
3589        indicate which Rook can castle if they are on the same side of King.
3590        For the initial position we grant rights to the outermost Rooks,
3591        and remember thos rights, and we then copy them on positions
3592        later in an FRC game. This means WB might not recognize castlings with
3593        Rooks that have moved back to their original position as illegal,
3594        but in ICS mode that is not its job anyway.
3595     */
3596     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3597     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3598
3599         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3600             if(board[0][i] == WhiteRook) j = i;
3601         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3602         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3603             if(board[0][i] == WhiteRook) j = i;
3604         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3605         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3606             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3607         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3608         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3609             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3610         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3611
3612         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3613         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3614             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3615         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3616             if(board[BOARD_HEIGHT-1][k] == bKing)
3617                 initialRights[5] = castlingRights[moveNum][5] = k;
3618     } else { int r;
3619         r = castlingRights[moveNum][0] = initialRights[0];
3620         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3621         r = castlingRights[moveNum][1] = initialRights[1];
3622         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3623         r = castlingRights[moveNum][3] = initialRights[3];
3624         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3625         r = castlingRights[moveNum][4] = initialRights[4];
3626         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3627         /* wildcastle kludge: always assume King has rights */
3628         r = castlingRights[moveNum][2] = initialRights[2];
3629         r = castlingRights[moveNum][5] = initialRights[5];
3630     }
3631     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3632     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3633
3634     
3635     if (ics_getting_history == H_GOT_REQ_HEADER ||
3636         ics_getting_history == H_GOT_UNREQ_HEADER) {
3637         /* This was an initial position from a move list, not
3638            the current position */
3639         return;
3640     }
3641     
3642     /* Update currentMove and known move number limits */
3643     newMove = newGame || moveNum > forwardMostMove;
3644
3645     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3646     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3647         takeback = forwardMostMove - moveNum;
3648         for (i = 0; i < takeback; i++) {
3649              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3650              SendToProgram("undo\n", &first);
3651         }
3652     }
3653
3654     if (newGame) {
3655         forwardMostMove = backwardMostMove = currentMove = moveNum;
3656         if (gameMode == IcsExamining && moveNum == 0) {
3657           /* Workaround for ICS limitation: we are not told the wild
3658              type when starting to examine a game.  But if we ask for
3659              the move list, the move list header will tell us */
3660             ics_getting_history = H_REQUESTED;
3661             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3662             SendToICS(str);
3663         }
3664     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3665                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3666         forwardMostMove = moveNum;
3667         if (!pausing || currentMove > forwardMostMove)
3668           currentMove = forwardMostMove;
3669     } else {
3670         /* New part of history that is not contiguous with old part */ 
3671         if (pausing && gameMode == IcsExamining) {
3672             pauseExamInvalid = TRUE;
3673             forwardMostMove = pauseExamForwardMostMove;
3674             return;
3675         }
3676         forwardMostMove = backwardMostMove = currentMove = moveNum;
3677         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3678             ics_getting_history = H_REQUESTED;
3679             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3680             SendToICS(str);
3681         }
3682     }
3683     
3684     /* Update the clocks */
3685     if (strchr(elapsed_time, '.')) {
3686       /* Time is in ms */
3687       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3688       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3689     } else {
3690       /* Time is in seconds */
3691       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3692       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3693     }
3694       
3695
3696 #if ZIPPY
3697     if (appData.zippyPlay && newGame &&
3698         gameMode != IcsObserving && gameMode != IcsIdle &&
3699         gameMode != IcsExamining)
3700       ZippyFirstBoard(moveNum, basetime, increment);
3701 #endif
3702     
3703     /* Put the move on the move list, first converting
3704        to canonical algebraic form. */
3705     if (moveNum > 0) {
3706   if (appData.debugMode) {
3707     if (appData.debugMode) { int f = forwardMostMove;
3708         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3709                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3710     }
3711     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3712     fprintf(debugFP, "moveNum = %d\n", moveNum);
3713     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3714     setbuf(debugFP, NULL);
3715   }
3716         if (moveNum <= backwardMostMove) {
3717             /* We don't know what the board looked like before
3718                this move.  Punt. */
3719             strcpy(parseList[moveNum - 1], move_str);
3720             strcat(parseList[moveNum - 1], " ");
3721             strcat(parseList[moveNum - 1], elapsed_time);
3722             moveList[moveNum - 1][0] = NULLCHAR;
3723         } else if (strcmp(move_str, "none") == 0) {
3724             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3725             /* Again, we don't know what the board looked like;
3726                this is really the start of the game. */
3727             parseList[moveNum - 1][0] = NULLCHAR;
3728             moveList[moveNum - 1][0] = NULLCHAR;
3729             backwardMostMove = moveNum;
3730             startedFromSetupPosition = TRUE;
3731             fromX = fromY = toX = toY = -1;
3732         } else {
3733           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3734           //                 So we parse the long-algebraic move string in stead of the SAN move
3735           int valid; char buf[MSG_SIZ], *prom;
3736
3737           // str looks something like "Q/a1-a2"; kill the slash
3738           if(str[1] == '/') 
3739                 sprintf(buf, "%c%s", str[0], str+2);
3740           else  strcpy(buf, str); // might be castling
3741           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3742                 strcat(buf, prom); // long move lacks promo specification!
3743           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3744                 if(appData.debugMode) 
3745                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3746                 strcpy(move_str, buf);
3747           }
3748           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3749                                 &fromX, &fromY, &toX, &toY, &promoChar)
3750                || ParseOneMove(buf, moveNum - 1, &moveType,
3751                                 &fromX, &fromY, &toX, &toY, &promoChar);
3752           // end of long SAN patch
3753           if (valid) {
3754             (void) CoordsToAlgebraic(boards[moveNum - 1],
3755                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3756                                      fromY, fromX, toY, toX, promoChar,
3757                                      parseList[moveNum-1]);
3758             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3759                              castlingRights[moveNum]) ) {
3760               case MT_NONE:
3761               case MT_STALEMATE:
3762               default:
3763                 break;
3764               case MT_CHECK:
3765                 if(gameInfo.variant != VariantShogi)
3766                     strcat(parseList[moveNum - 1], "+");
3767                 break;
3768               case MT_CHECKMATE:
3769               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3770                 strcat(parseList[moveNum - 1], "#");
3771                 break;
3772             }
3773             strcat(parseList[moveNum - 1], " ");
3774             strcat(parseList[moveNum - 1], elapsed_time);
3775             /* currentMoveString is set as a side-effect of ParseOneMove */
3776             strcpy(moveList[moveNum - 1], currentMoveString);
3777             strcat(moveList[moveNum - 1], "\n");
3778           } else {
3779             /* Move from ICS was illegal!?  Punt. */
3780   if (appData.debugMode) {
3781     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3782     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3783   }
3784             strcpy(parseList[moveNum - 1], move_str);
3785             strcat(parseList[moveNum - 1], " ");
3786             strcat(parseList[moveNum - 1], elapsed_time);
3787             moveList[moveNum - 1][0] = NULLCHAR;
3788             fromX = fromY = toX = toY = -1;
3789           }
3790         }
3791   if (appData.debugMode) {
3792     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3793     setbuf(debugFP, NULL);
3794   }
3795
3796 #if ZIPPY
3797         /* Send move to chess program (BEFORE animating it). */
3798         if (appData.zippyPlay && !newGame && newMove && 
3799            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3800
3801             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3802                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3803                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3804                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3805                             move_str);
3806                     DisplayError(str, 0);
3807                 } else {
3808                     if (first.sendTime) {
3809                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3810                     }
3811                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3812                     if (firstMove && !bookHit) {
3813                         firstMove = FALSE;
3814                         if (first.useColors) {
3815                           SendToProgram(gameMode == IcsPlayingWhite ?
3816                                         "white\ngo\n" :
3817                                         "black\ngo\n", &first);
3818                         } else {
3819                           SendToProgram("go\n", &first);
3820                         }
3821                         first.maybeThinking = TRUE;
3822                     }
3823                 }
3824             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3825               if (moveList[moveNum - 1][0] == NULLCHAR) {
3826                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3827                 DisplayError(str, 0);
3828               } else {
3829                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3830                 SendMoveToProgram(moveNum - 1, &first);
3831               }
3832             }
3833         }
3834 #endif
3835     }
3836
3837     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3838         /* If move comes from a remote source, animate it.  If it
3839            isn't remote, it will have already been animated. */
3840         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3841             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3842         }
3843         if (!pausing && appData.highlightLastMove) {
3844             SetHighlights(fromX, fromY, toX, toY);
3845         }
3846     }
3847     
3848     /* Start the clocks */
3849     whiteFlag = blackFlag = FALSE;
3850     appData.clockMode = !(basetime == 0 && increment == 0);
3851     if (ticking == 0) {
3852       ics_clock_paused = TRUE;
3853       StopClocks();
3854     } else if (ticking == 1) {
3855       ics_clock_paused = FALSE;
3856     }
3857     if (gameMode == IcsIdle ||
3858         relation == RELATION_OBSERVING_STATIC ||
3859         relation == RELATION_EXAMINING ||
3860         ics_clock_paused)
3861       DisplayBothClocks();
3862     else
3863       StartClocks();
3864     
3865     /* Display opponents and material strengths */
3866     if (gameInfo.variant != VariantBughouse &&
3867         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3868         if (tinyLayout || smallLayout) {
3869             if(gameInfo.variant == VariantNormal)
3870                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3871                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3872                     basetime, increment);
3873             else
3874                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3875                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3876                     basetime, increment, (int) gameInfo.variant);
3877         } else {
3878             if(gameInfo.variant == VariantNormal)
3879                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3880                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3881                     basetime, increment);
3882             else
3883                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3884                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3885                     basetime, increment, VariantName(gameInfo.variant));
3886         }
3887         DisplayTitle(str);
3888   if (appData.debugMode) {
3889     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3890   }
3891     }
3892
3893    
3894     /* Display the board */
3895     if (!pausing && !appData.noGUI) {
3896       
3897       if (appData.premove)
3898           if (!gotPremove || 
3899              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3900              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3901               ClearPremoveHighlights();
3902
3903       DrawPosition(FALSE, boards[currentMove]);
3904       DisplayMove(moveNum - 1);
3905       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3906             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3907               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3908         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3909       }
3910     }
3911
3912     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3913 #if ZIPPY
3914     if(bookHit) { // [HGM] book: simulate book reply
3915         static char bookMove[MSG_SIZ]; // a bit generous?
3916
3917         programStats.nodes = programStats.depth = programStats.time = 
3918         programStats.score = programStats.got_only_move = 0;
3919         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3920
3921         strcpy(bookMove, "move ");
3922         strcat(bookMove, bookHit);
3923         HandleMachineMove(bookMove, &first);
3924     }
3925 #endif
3926 }
3927
3928 void
3929 GetMoveListEvent()
3930 {
3931     char buf[MSG_SIZ];
3932     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3933         ics_getting_history = H_REQUESTED;
3934         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3935         SendToICS(buf);
3936     }
3937 }
3938
3939 void
3940 AnalysisPeriodicEvent(force)
3941      int force;
3942 {
3943     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3944          && !force) || !appData.periodicUpdates)
3945       return;
3946
3947     /* Send . command to Crafty to collect stats */
3948     SendToProgram(".\n", &first);
3949
3950     /* Don't send another until we get a response (this makes
3951        us stop sending to old Crafty's which don't understand
3952        the "." command (sending illegal cmds resets node count & time,
3953        which looks bad)) */
3954     programStats.ok_to_send = 0;
3955 }
3956
3957 void ics_update_width(new_width)
3958         int new_width;
3959 {
3960         ics_printf("set width %d\n", new_width);
3961 }
3962
3963 void
3964 SendMoveToProgram(moveNum, cps)
3965      int moveNum;
3966      ChessProgramState *cps;
3967 {
3968     char buf[MSG_SIZ];
3969
3970     if (cps->useUsermove) {
3971       SendToProgram("usermove ", cps);
3972     }
3973     if (cps->useSAN) {
3974       char *space;
3975       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3976         int len = space - parseList[moveNum];
3977         memcpy(buf, parseList[moveNum], len);
3978         buf[len++] = '\n';
3979         buf[len] = NULLCHAR;
3980       } else {
3981         sprintf(buf, "%s\n", parseList[moveNum]);
3982       }
3983       SendToProgram(buf, cps);
3984     } else {
3985       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3986         AlphaRank(moveList[moveNum], 4);
3987         SendToProgram(moveList[moveNum], cps);
3988         AlphaRank(moveList[moveNum], 4); // and back
3989       } else
3990       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3991        * the engine. It would be nice to have a better way to identify castle 
3992        * moves here. */
3993       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3994                                                                          && cps->useOOCastle) {
3995         int fromX = moveList[moveNum][0] - AAA; 
3996         int fromY = moveList[moveNum][1] - ONE;
3997         int toX = moveList[moveNum][2] - AAA; 
3998         int toY = moveList[moveNum][3] - ONE;
3999         if((boards[moveNum][fromY][fromX] == WhiteKing 
4000             && boards[moveNum][toY][toX] == WhiteRook)
4001            || (boards[moveNum][fromY][fromX] == BlackKing 
4002                && boards[moveNum][toY][toX] == BlackRook)) {
4003           if(toX > fromX) SendToProgram("O-O\n", cps);
4004           else SendToProgram("O-O-O\n", cps);
4005         }
4006         else SendToProgram(moveList[moveNum], cps);
4007       }
4008       else SendToProgram(moveList[moveNum], cps);
4009       /* End of additions by Tord */
4010     }
4011
4012     /* [HGM] setting up the opening has brought engine in force mode! */
4013     /*       Send 'go' if we are in a mode where machine should play. */
4014     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4015         (gameMode == TwoMachinesPlay   ||
4016 #ifdef ZIPPY
4017          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4018 #endif
4019          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4020         SendToProgram("go\n", cps);
4021   if (appData.debugMode) {
4022     fprintf(debugFP, "(extra)\n");
4023   }
4024     }
4025     setboardSpoiledMachineBlack = 0;
4026 }
4027
4028 void
4029 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4030      ChessMove moveType;
4031      int fromX, fromY, toX, toY;
4032 {
4033     char user_move[MSG_SIZ];
4034
4035     switch (moveType) {
4036       default:
4037         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4038                 (int)moveType, fromX, fromY, toX, toY);
4039         DisplayError(user_move + strlen("say "), 0);
4040         break;
4041       case WhiteKingSideCastle:
4042       case BlackKingSideCastle:
4043       case WhiteQueenSideCastleWild:
4044       case BlackQueenSideCastleWild:
4045       /* PUSH Fabien */
4046       case WhiteHSideCastleFR:
4047       case BlackHSideCastleFR:
4048       /* POP Fabien */
4049         sprintf(user_move, "o-o\n");
4050         break;
4051       case WhiteQueenSideCastle:
4052       case BlackQueenSideCastle:
4053       case WhiteKingSideCastleWild:
4054       case BlackKingSideCastleWild:
4055       /* PUSH Fabien */
4056       case WhiteASideCastleFR:
4057       case BlackASideCastleFR:
4058       /* POP Fabien */
4059         sprintf(user_move, "o-o-o\n");
4060         break;
4061       case WhitePromotionQueen:
4062       case BlackPromotionQueen:
4063       case WhitePromotionRook:
4064       case BlackPromotionRook:
4065       case WhitePromotionBishop:
4066       case BlackPromotionBishop:
4067       case WhitePromotionKnight:
4068       case BlackPromotionKnight:
4069       case WhitePromotionKing:
4070       case BlackPromotionKing:
4071       case WhitePromotionChancellor:
4072       case BlackPromotionChancellor:
4073       case WhitePromotionArchbishop:
4074       case BlackPromotionArchbishop:
4075         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4076             sprintf(user_move, "%c%c%c%c=%c\n",
4077                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4078                 PieceToChar(WhiteFerz));
4079         else if(gameInfo.variant == VariantGreat)
4080             sprintf(user_move, "%c%c%c%c=%c\n",
4081                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4082                 PieceToChar(WhiteMan));
4083         else
4084             sprintf(user_move, "%c%c%c%c=%c\n",
4085                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4086                 PieceToChar(PromoPiece(moveType)));
4087         break;
4088       case WhiteDrop:
4089       case BlackDrop:
4090         sprintf(user_move, "%c@%c%c\n",
4091                 ToUpper(PieceToChar((ChessSquare) fromX)),
4092                 AAA + toX, ONE + toY);
4093         break;
4094       case NormalMove:
4095       case WhiteCapturesEnPassant:
4096       case BlackCapturesEnPassant:
4097       case IllegalMove:  /* could be a variant we don't quite understand */
4098         sprintf(user_move, "%c%c%c%c\n",
4099                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4100         break;
4101     }
4102     SendToICS(user_move);
4103     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4104         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4105 }
4106
4107 void
4108 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4109      int rf, ff, rt, ft;
4110      char promoChar;
4111      char move[7];
4112 {
4113     if (rf == DROP_RANK) {
4114         sprintf(move, "%c@%c%c\n",
4115                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4116     } else {
4117         if (promoChar == 'x' || promoChar == NULLCHAR) {
4118             sprintf(move, "%c%c%c%c\n",
4119                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4120         } else {
4121             sprintf(move, "%c%c%c%c%c\n",
4122                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4123         }
4124     }
4125 }
4126
4127 void
4128 ProcessICSInitScript(f)
4129      FILE *f;
4130 {
4131     char buf[MSG_SIZ];
4132
4133     while (fgets(buf, MSG_SIZ, f)) {
4134         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4135     }
4136
4137     fclose(f);
4138 }
4139
4140
4141 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4142 void
4143 AlphaRank(char *move, int n)
4144 {
4145 //    char *p = move, c; int x, y;
4146
4147     if (appData.debugMode) {
4148         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4149     }
4150
4151     if(move[1]=='*' && 
4152        move[2]>='0' && move[2]<='9' &&
4153        move[3]>='a' && move[3]<='x'    ) {
4154         move[1] = '@';
4155         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4156         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4157     } else
4158     if(move[0]>='0' && move[0]<='9' &&
4159        move[1]>='a' && move[1]<='x' &&
4160        move[2]>='0' && move[2]<='9' &&
4161        move[3]>='a' && move[3]<='x'    ) {
4162         /* input move, Shogi -> normal */
4163         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4164         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4165         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4166         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4167     } else
4168     if(move[1]=='@' &&
4169        move[3]>='0' && move[3]<='9' &&
4170        move[2]>='a' && move[2]<='x'    ) {
4171         move[1] = '*';
4172         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4173         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4174     } else
4175     if(
4176        move[0]>='a' && move[0]<='x' &&
4177        move[3]>='0' && move[3]<='9' &&
4178        move[2]>='a' && move[2]<='x'    ) {
4179          /* output move, normal -> Shogi */
4180         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4181         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4182         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4183         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4184         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4185     }
4186     if (appData.debugMode) {
4187         fprintf(debugFP, "   out = '%s'\n", move);
4188     }
4189 }
4190
4191 /* Parser for moves from gnuchess, ICS, or user typein box */
4192 Boolean
4193 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4194      char *move;
4195      int moveNum;
4196      ChessMove *moveType;
4197      int *fromX, *fromY, *toX, *toY;
4198      char *promoChar;
4199 {       
4200     if (appData.debugMode) {
4201         fprintf(debugFP, "move to parse: %s\n", move);
4202     }
4203     *moveType = yylexstr(moveNum, move);
4204
4205     switch (*moveType) {
4206       case WhitePromotionChancellor:
4207       case BlackPromotionChancellor:
4208       case WhitePromotionArchbishop:
4209       case BlackPromotionArchbishop:
4210       case WhitePromotionQueen:
4211       case BlackPromotionQueen:
4212       case WhitePromotionRook:
4213       case BlackPromotionRook:
4214       case WhitePromotionBishop:
4215       case BlackPromotionBishop:
4216       case WhitePromotionKnight:
4217       case BlackPromotionKnight:
4218       case WhitePromotionKing:
4219       case BlackPromotionKing:
4220       case NormalMove:
4221       case WhiteCapturesEnPassant:
4222       case BlackCapturesEnPassant:
4223       case WhiteKingSideCastle:
4224       case WhiteQueenSideCastle:
4225       case BlackKingSideCastle:
4226       case BlackQueenSideCastle:
4227       case WhiteKingSideCastleWild:
4228       case WhiteQueenSideCastleWild:
4229       case BlackKingSideCastleWild:
4230       case BlackQueenSideCastleWild:
4231       /* Code added by Tord: */
4232       case WhiteHSideCastleFR:
4233       case WhiteASideCastleFR:
4234       case BlackHSideCastleFR:
4235       case BlackASideCastleFR:
4236       /* End of code added by Tord */
4237       case IllegalMove:         /* bug or odd chess variant */
4238         *fromX = currentMoveString[0] - AAA;
4239         *fromY = currentMoveString[1] - ONE;
4240         *toX = currentMoveString[2] - AAA;
4241         *toY = currentMoveString[3] - ONE;
4242         *promoChar = currentMoveString[4];
4243         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4244             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4245     if (appData.debugMode) {
4246         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4247     }
4248             *fromX = *fromY = *toX = *toY = 0;
4249             return FALSE;
4250         }
4251         if (appData.testLegality) {
4252           return (*moveType != IllegalMove);
4253         } else {
4254           return !(fromX == fromY && toX == toY);
4255         }
4256
4257       case WhiteDrop:
4258       case BlackDrop:
4259         *fromX = *moveType == WhiteDrop ?
4260           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4261           (int) CharToPiece(ToLower(currentMoveString[0]));
4262         *fromY = DROP_RANK;
4263         *toX = currentMoveString[2] - AAA;
4264         *toY = currentMoveString[3] - ONE;
4265         *promoChar = NULLCHAR;
4266         return TRUE;
4267
4268       case AmbiguousMove:
4269       case ImpossibleMove:
4270       case (ChessMove) 0:       /* end of file */
4271       case ElapsedTime:
4272       case Comment:
4273       case PGNTag:
4274       case NAG:
4275       case WhiteWins:
4276       case BlackWins:
4277       case GameIsDrawn:
4278       default:
4279     if (appData.debugMode) {
4280         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4281     }
4282         /* bug? */
4283         *fromX = *fromY = *toX = *toY = 0;
4284         *promoChar = NULLCHAR;
4285         return FALSE;
4286     }
4287 }
4288
4289 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4290 // All positions will have equal probability, but the current method will not provide a unique
4291 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4292 #define DARK 1
4293 #define LITE 2
4294 #define ANY 3
4295
4296 int squaresLeft[4];
4297 int piecesLeft[(int)BlackPawn];
4298 int seed, nrOfShuffles;
4299
4300 void GetPositionNumber()
4301 {       // sets global variable seed
4302         int i;
4303
4304         seed = appData.defaultFrcPosition;
4305         if(seed < 0) { // randomize based on time for negative FRC position numbers
4306                 for(i=0; i<50; i++) seed += random();
4307                 seed = random() ^ random() >> 8 ^ random() << 8;
4308                 if(seed<0) seed = -seed;
4309         }
4310 }
4311
4312 int put(Board board, int pieceType, int rank, int n, int shade)
4313 // put the piece on the (n-1)-th empty squares of the given shade
4314 {
4315         int i;
4316
4317         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4318                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4319                         board[rank][i] = (ChessSquare) pieceType;
4320                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4321                         squaresLeft[ANY]--;
4322                         piecesLeft[pieceType]--; 
4323                         return i;
4324                 }
4325         }
4326         return -1;
4327 }
4328
4329
4330 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4331 // calculate where the next piece goes, (any empty square), and put it there
4332 {
4333         int i;
4334
4335         i = seed % squaresLeft[shade];
4336         nrOfShuffles *= squaresLeft[shade];
4337         seed /= squaresLeft[shade];
4338         put(board, pieceType, rank, i, shade);
4339 }
4340
4341 void AddTwoPieces(Board board, int pieceType, int rank)
4342 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4343 {
4344         int i, n=squaresLeft[ANY], j=n-1, k;
4345
4346         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4347         i = seed % k;  // pick one
4348         nrOfShuffles *= k;
4349         seed /= k;
4350         while(i >= j) i -= j--;
4351         j = n - 1 - j; i += j;
4352         put(board, pieceType, rank, j, ANY);
4353         put(board, pieceType, rank, i, ANY);
4354 }
4355
4356 void SetUpShuffle(Board board, int number)
4357 {
4358         int i, p, first=1;
4359
4360         GetPositionNumber(); nrOfShuffles = 1;
4361
4362         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4363         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4364         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4365
4366         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4367
4368         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4369             p = (int) board[0][i];
4370             if(p < (int) BlackPawn) piecesLeft[p] ++;
4371             board[0][i] = EmptySquare;
4372         }
4373
4374         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4375             // shuffles restricted to allow normal castling put KRR first
4376             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4377                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4378             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4379                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4380             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4381                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4382             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4383                 put(board, WhiteRook, 0, 0, ANY);
4384             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4385         }
4386
4387         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4388             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4389             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4390                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4391                 while(piecesLeft[p] >= 2) {
4392                     AddOnePiece(board, p, 0, LITE);
4393                     AddOnePiece(board, p, 0, DARK);
4394                 }
4395                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4396             }
4397
4398         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4399             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4400             // but we leave King and Rooks for last, to possibly obey FRC restriction
4401             if(p == (int)WhiteRook) continue;
4402             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4403             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4404         }
4405
4406         // now everything is placed, except perhaps King (Unicorn) and Rooks
4407
4408         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4409             // Last King gets castling rights
4410             while(piecesLeft[(int)WhiteUnicorn]) {
4411                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4412                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4413             }
4414
4415             while(piecesLeft[(int)WhiteKing]) {
4416                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4417                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4418             }
4419
4420
4421         } else {
4422             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4423             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4424         }
4425
4426         // Only Rooks can be left; simply place them all
4427         while(piecesLeft[(int)WhiteRook]) {
4428                 i = put(board, WhiteRook, 0, 0, ANY);
4429                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4430                         if(first) {
4431                                 first=0;
4432                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4433                         }
4434                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4435                 }
4436         }
4437         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4438             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4439         }
4440
4441         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4442 }
4443
4444 int SetCharTable( char *table, const char * map )
4445 /* [HGM] moved here from winboard.c because of its general usefulness */
4446 /*       Basically a safe strcpy that uses the last character as King */
4447 {
4448     int result = FALSE; int NrPieces;
4449
4450     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4451                     && NrPieces >= 12 && !(NrPieces&1)) {
4452         int i; /* [HGM] Accept even length from 12 to 34 */
4453
4454         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4455         for( i=0; i<NrPieces/2-1; i++ ) {
4456             table[i] = map[i];
4457             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4458         }
4459         table[(int) WhiteKing]  = map[NrPieces/2-1];
4460         table[(int) BlackKing]  = map[NrPieces-1];
4461
4462         result = TRUE;
4463     }
4464
4465     return result;
4466 }
4467
4468 void Prelude(Board board)
4469 {       // [HGM] superchess: random selection of exo-pieces
4470         int i, j, k; ChessSquare p; 
4471         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4472
4473         GetPositionNumber(); // use FRC position number
4474
4475         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4476             SetCharTable(pieceToChar, appData.pieceToCharTable);
4477             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4478                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4479         }
4480
4481         j = seed%4;                 seed /= 4; 
4482         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4483         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4484         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4485         j = seed%3 + (seed%3 >= j); seed /= 3; 
4486         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4487         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4488         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4489         j = seed%3;                 seed /= 3; 
4490         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4491         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4492         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4493         j = seed%2 + (seed%2 >= j); seed /= 2; 
4494         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4495         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4496         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4497         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4498         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4499         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4500         put(board, exoPieces[0],    0, 0, ANY);
4501         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4502 }
4503
4504 void
4505 InitPosition(redraw)
4506      int redraw;
4507 {
4508     ChessSquare (* pieces)[BOARD_SIZE];
4509     int i, j, pawnRow, overrule,
4510     oldx = gameInfo.boardWidth,
4511     oldy = gameInfo.boardHeight,
4512     oldh = gameInfo.holdingsWidth,
4513     oldv = gameInfo.variant;
4514
4515     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4516
4517     /* [AS] Initialize pv info list [HGM] and game status */
4518     {
4519         for( i=0; i<MAX_MOVES; i++ ) {
4520             pvInfoList[i].depth = 0;
4521             epStatus[i]=EP_NONE;
4522             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4523         }
4524
4525         initialRulePlies = 0; /* 50-move counter start */
4526
4527         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4528         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4529     }
4530
4531     
4532     /* [HGM] logic here is completely changed. In stead of full positions */
4533     /* the initialized data only consist of the two backranks. The switch */
4534     /* selects which one we will use, which is than copied to the Board   */
4535     /* initialPosition, which for the rest is initialized by Pawns and    */
4536     /* empty squares. This initial position is then copied to boards[0],  */
4537     /* possibly after shuffling, so that it remains available.            */
4538
4539     gameInfo.holdingsWidth = 0; /* default board sizes */
4540     gameInfo.boardWidth    = 8;
4541     gameInfo.boardHeight   = 8;
4542     gameInfo.holdingsSize  = 0;
4543     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4544     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4545     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4546
4547     switch (gameInfo.variant) {
4548     case VariantFischeRandom:
4549       shuffleOpenings = TRUE;
4550     default:
4551       pieces = FIDEArray;
4552       break;
4553     case VariantShatranj:
4554       pieces = ShatranjArray;
4555       nrCastlingRights = 0;
4556       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4557       break;
4558     case VariantTwoKings:
4559       pieces = twoKingsArray;
4560       break;
4561     case VariantCapaRandom:
4562       shuffleOpenings = TRUE;
4563     case VariantCapablanca:
4564       pieces = CapablancaArray;
4565       gameInfo.boardWidth = 10;
4566       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4567       break;
4568     case VariantGothic:
4569       pieces = GothicArray;
4570       gameInfo.boardWidth = 10;
4571       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4572       break;
4573     case VariantJanus:
4574       pieces = JanusArray;
4575       gameInfo.boardWidth = 10;
4576       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4577       nrCastlingRights = 6;
4578         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4579         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4580         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4581         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4582         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4583         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4584       break;
4585     case VariantFalcon:
4586       pieces = FalconArray;
4587       gameInfo.boardWidth = 10;
4588       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4589       break;
4590     case VariantXiangqi:
4591       pieces = XiangqiArray;
4592       gameInfo.boardWidth  = 9;
4593       gameInfo.boardHeight = 10;
4594       nrCastlingRights = 0;
4595       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4596       break;
4597     case VariantShogi:
4598       pieces = ShogiArray;
4599       gameInfo.boardWidth  = 9;
4600       gameInfo.boardHeight = 9;
4601       gameInfo.holdingsSize = 7;
4602       nrCastlingRights = 0;
4603       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4604       break;
4605     case VariantCourier:
4606       pieces = CourierArray;
4607       gameInfo.boardWidth  = 12;
4608       nrCastlingRights = 0;
4609       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4610       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4611       break;
4612     case VariantKnightmate:
4613       pieces = KnightmateArray;
4614       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4615       break;
4616     case VariantFairy:
4617       pieces = fairyArray;
4618       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4619       break;
4620     case VariantGreat:
4621       pieces = GreatArray;
4622       gameInfo.boardWidth = 10;
4623       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4624       gameInfo.holdingsSize = 8;
4625       break;
4626     case VariantSuper:
4627       pieces = FIDEArray;
4628       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4629       gameInfo.holdingsSize = 8;
4630       startedFromSetupPosition = TRUE;
4631       break;
4632     case VariantCrazyhouse:
4633     case VariantBughouse:
4634       pieces = FIDEArray;
4635       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4636       gameInfo.holdingsSize = 5;
4637       break;
4638     case VariantWildCastle:
4639       pieces = FIDEArray;
4640       /* !!?shuffle with kings guaranteed to be on d or e file */
4641       shuffleOpenings = 1;
4642       break;
4643     case VariantNoCastle:
4644       pieces = FIDEArray;
4645       nrCastlingRights = 0;
4646       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4647       /* !!?unconstrained back-rank shuffle */
4648       shuffleOpenings = 1;
4649       break;
4650     }
4651
4652     overrule = 0;
4653     if(appData.NrFiles >= 0) {
4654         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4655         gameInfo.boardWidth = appData.NrFiles;
4656     }
4657     if(appData.NrRanks >= 0) {
4658         gameInfo.boardHeight = appData.NrRanks;
4659     }
4660     if(appData.holdingsSize >= 0) {
4661         i = appData.holdingsSize;
4662         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4663         gameInfo.holdingsSize = i;
4664     }
4665     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4666     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4667         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4668
4669     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4670     if(pawnRow < 1) pawnRow = 1;
4671
4672     /* User pieceToChar list overrules defaults */
4673     if(appData.pieceToCharTable != NULL)
4674         SetCharTable(pieceToChar, appData.pieceToCharTable);
4675
4676     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4677
4678         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4679             s = (ChessSquare) 0; /* account holding counts in guard band */
4680         for( i=0; i<BOARD_HEIGHT; i++ )
4681             initialPosition[i][j] = s;
4682
4683         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4684         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4685         initialPosition[pawnRow][j] = WhitePawn;
4686         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4687         if(gameInfo.variant == VariantXiangqi) {
4688             if(j&1) {
4689                 initialPosition[pawnRow][j] = 
4690                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4691                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4692                    initialPosition[2][j] = WhiteCannon;
4693                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4694                 }
4695             }
4696         }
4697         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4698     }
4699     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4700
4701             j=BOARD_LEFT+1;
4702             initialPosition[1][j] = WhiteBishop;
4703             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4704             j=BOARD_RGHT-2;
4705             initialPosition[1][j] = WhiteRook;
4706             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4707     }
4708
4709     if( nrCastlingRights == -1) {
4710         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4711         /*       This sets default castling rights from none to normal corners   */
4712         /* Variants with other castling rights must set them themselves above    */
4713         nrCastlingRights = 6;
4714        
4715         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4716         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4717         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4718         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4719         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4720         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4721      }
4722
4723      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4724      if(gameInfo.variant == VariantGreat) { // promotion commoners
4725         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4726         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4727         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4728         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4729      }
4730   if (appData.debugMode) {
4731     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4732   }
4733     if(shuffleOpenings) {
4734         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4735         startedFromSetupPosition = TRUE;
4736     }
4737     if(startedFromPositionFile) {
4738       /* [HGM] loadPos: use PositionFile for every new game */
4739       CopyBoard(initialPosition, filePosition);
4740       for(i=0; i<nrCastlingRights; i++)
4741           castlingRights[0][i] = initialRights[i] = fileRights[i];
4742       startedFromSetupPosition = TRUE;
4743     }
4744
4745     CopyBoard(boards[0], initialPosition);
4746
4747     if(oldx != gameInfo.boardWidth ||
4748        oldy != gameInfo.boardHeight ||
4749        oldh != gameInfo.holdingsWidth
4750 #ifdef GOTHIC
4751        || oldv == VariantGothic ||        // For licensing popups
4752        gameInfo.variant == VariantGothic
4753 #endif
4754 #ifdef FALCON
4755        || oldv == VariantFalcon ||
4756        gameInfo.variant == VariantFalcon
4757 #endif
4758                                          )
4759             InitDrawingSizes(-2 ,0);
4760
4761     if (redraw)
4762       DrawPosition(TRUE, boards[currentMove]);
4763 }
4764
4765 void
4766 SendBoard(cps, moveNum)
4767      ChessProgramState *cps;
4768      int moveNum;
4769 {
4770     char message[MSG_SIZ];
4771     
4772     if (cps->useSetboard) {
4773       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4774       sprintf(message, "setboard %s\n", fen);
4775       SendToProgram(message, cps);
4776       free(fen);
4777
4778     } else {
4779       ChessSquare *bp;
4780       int i, j;
4781       /* Kludge to set black to move, avoiding the troublesome and now
4782        * deprecated "black" command.
4783        */
4784       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4785
4786       SendToProgram("edit\n", cps);
4787       SendToProgram("#\n", cps);
4788       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4789         bp = &boards[moveNum][i][BOARD_LEFT];
4790         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4791           if ((int) *bp < (int) BlackPawn) {
4792             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4793                     AAA + j, ONE + i);
4794             if(message[0] == '+' || message[0] == '~') {
4795                 sprintf(message, "%c%c%c+\n",
4796                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4797                         AAA + j, ONE + i);
4798             }
4799             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4800                 message[1] = BOARD_RGHT   - 1 - j + '1';
4801                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4802             }
4803             SendToProgram(message, cps);
4804           }
4805         }
4806       }
4807     
4808       SendToProgram("c\n", cps);
4809       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4810         bp = &boards[moveNum][i][BOARD_LEFT];
4811         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4812           if (((int) *bp != (int) EmptySquare)
4813               && ((int) *bp >= (int) BlackPawn)) {
4814             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4815                     AAA + j, ONE + i);
4816             if(message[0] == '+' || message[0] == '~') {
4817                 sprintf(message, "%c%c%c+\n",
4818                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4819                         AAA + j, ONE + i);
4820             }
4821             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4822                 message[1] = BOARD_RGHT   - 1 - j + '1';
4823                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4824             }
4825             SendToProgram(message, cps);
4826           }
4827         }
4828       }
4829     
4830       SendToProgram(".\n", cps);
4831     }
4832     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4833 }
4834
4835 int
4836 IsPromotion(fromX, fromY, toX, toY)
4837      int fromX, fromY, toX, toY;
4838 {
4839     /* [HGM] add Shogi promotions */
4840     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4841     ChessSquare piece;
4842
4843     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4844       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4845    /* [HGM] Note to self: line above also weeds out drops */
4846     piece = boards[currentMove][fromY][fromX];
4847     if(gameInfo.variant == VariantShogi) {
4848         promotionZoneSize = 3;
4849         highestPromotingPiece = (int)WhiteKing;
4850         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4851            and if in normal chess we then allow promotion to King, why not
4852            allow promotion of other piece in Shogi?                         */
4853     }
4854     if((int)piece >= BlackPawn) {
4855         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4856              return FALSE;
4857         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4858     } else {
4859         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4860            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4861     }
4862     return ( (int)piece <= highestPromotingPiece );
4863 }
4864
4865 int
4866 InPalace(row, column)
4867      int row, column;
4868 {   /* [HGM] for Xiangqi */
4869     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4870          column < (BOARD_WIDTH + 4)/2 &&
4871          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4872     return FALSE;
4873 }
4874
4875 int
4876 PieceForSquare (x, y)
4877      int x;
4878      int y;
4879 {
4880   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4881      return -1;
4882   else
4883      return boards[currentMove][y][x];
4884 }
4885
4886 int
4887 OKToStartUserMove(x, y)
4888      int x, y;
4889 {
4890     ChessSquare from_piece;
4891     int white_piece;
4892
4893     if (matchMode) return FALSE;
4894     if (gameMode == EditPosition) return TRUE;
4895
4896     if (x >= 0 && y >= 0)
4897       from_piece = boards[currentMove][y][x];
4898     else
4899       from_piece = EmptySquare;
4900
4901     if (from_piece == EmptySquare) return FALSE;
4902
4903     white_piece = (int)from_piece >= (int)WhitePawn &&
4904       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4905
4906     switch (gameMode) {
4907       case PlayFromGameFile:
4908       case AnalyzeFile:
4909       case TwoMachinesPlay:
4910       case EndOfGame:
4911         return FALSE;
4912
4913       case IcsObserving:
4914       case IcsIdle:
4915         return FALSE;
4916
4917       case MachinePlaysWhite:
4918       case IcsPlayingBlack:
4919         if (appData.zippyPlay) return FALSE;
4920         if (white_piece) {
4921             DisplayMoveError(_("You are playing Black"));
4922             return FALSE;
4923         }
4924         break;
4925
4926       case MachinePlaysBlack:
4927       case IcsPlayingWhite:
4928         if (appData.zippyPlay) return FALSE;
4929         if (!white_piece) {
4930             DisplayMoveError(_("You are playing White"));
4931             return FALSE;
4932         }
4933         break;
4934
4935       case EditGame:
4936         if (!white_piece && WhiteOnMove(currentMove)) {
4937             DisplayMoveError(_("It is White's turn"));
4938             return FALSE;
4939         }           
4940         if (white_piece && !WhiteOnMove(currentMove)) {
4941             DisplayMoveError(_("It is Black's turn"));
4942             return FALSE;
4943         }           
4944         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4945             /* Editing correspondence game history */
4946             /* Could disallow this or prompt for confirmation */
4947             cmailOldMove = -1;
4948         }
4949         if (currentMove < forwardMostMove) {
4950             /* Discarding moves */
4951             /* Could prompt for confirmation here,
4952                but I don't think that's such a good idea */
4953             forwardMostMove = currentMove;
4954         }
4955         break;
4956
4957       case BeginningOfGame:
4958         if (appData.icsActive) return FALSE;
4959         if (!appData.noChessProgram) {
4960             if (!white_piece) {
4961                 DisplayMoveError(_("You are playing White"));
4962                 return FALSE;
4963             }
4964         }
4965         break;
4966         
4967       case Training:
4968         if (!white_piece && WhiteOnMove(currentMove)) {
4969             DisplayMoveError(_("It is White's turn"));
4970             return FALSE;
4971         }           
4972         if (white_piece && !WhiteOnMove(currentMove)) {
4973             DisplayMoveError(_("It is Black's turn"));
4974             return FALSE;
4975         }           
4976         break;
4977
4978       default:
4979       case IcsExamining:
4980         break;
4981     }
4982     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4983         && gameMode != AnalyzeFile && gameMode != Training) {
4984         DisplayMoveError(_("Displayed position is not current"));
4985         return FALSE;
4986     }
4987     return TRUE;
4988 }
4989
4990 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4991 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4992 int lastLoadGameUseList = FALSE;
4993 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4994 ChessMove lastLoadGameStart = (ChessMove) 0;
4995
4996 ChessMove
4997 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4998      int fromX, fromY, toX, toY;
4999      int promoChar;
5000      Boolean captureOwn;
5001 {
5002     ChessMove moveType;
5003     ChessSquare pdown, pup;
5004
5005     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5006
5007     /* [HGM] suppress all moves into holdings area and guard band */
5008     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5009             return ImpossibleMove;
5010
5011     /* [HGM] <sameColor> moved to here from winboard.c */
5012     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5013     pdown = boards[currentMove][fromY][fromX];
5014     pup = boards[currentMove][toY][toX];
5015     if (    gameMode != EditPosition && !captureOwn &&
5016             (WhitePawn <= pdown && pdown < BlackPawn &&
5017              WhitePawn <= pup && pup < BlackPawn  ||
5018              BlackPawn <= pdown && pdown < EmptySquare &&
5019              BlackPawn <= pup && pup < EmptySquare 
5020             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5021                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5022                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5023                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5024                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5025         )           )
5026          return Comment;
5027
5028     /* Check if the user is playing in turn.  This is complicated because we
5029        let the user "pick up" a piece before it is his turn.  So the piece he
5030        tried to pick up may have been captured by the time he puts it down!
5031        Therefore we use the color the user is supposed to be playing in this
5032        test, not the color of the piece that is currently on the starting
5033        square---except in EditGame mode, where the user is playing both
5034        sides; fortunately there the capture race can't happen.  (It can
5035        now happen in IcsExamining mode, but that's just too bad.  The user
5036        will get a somewhat confusing message in that case.)
5037        */
5038
5039     switch (gameMode) {
5040       case PlayFromGameFile:
5041       case AnalyzeFile:
5042       case TwoMachinesPlay:
5043       case EndOfGame:
5044       case IcsObserving:
5045       case IcsIdle:
5046         /* We switched into a game mode where moves are not accepted,
5047            perhaps while the mouse button was down. */
5048         return ImpossibleMove;
5049
5050       case MachinePlaysWhite:
5051         /* User is moving for Black */
5052         if (WhiteOnMove(currentMove)) {
5053             DisplayMoveError(_("It is White's turn"));
5054             return ImpossibleMove;
5055         }
5056         break;
5057
5058       case MachinePlaysBlack:
5059         /* User is moving for White */
5060         if (!WhiteOnMove(currentMove)) {
5061             DisplayMoveError(_("It is Black's turn"));
5062             return ImpossibleMove;
5063         }
5064         break;
5065
5066       case EditGame:
5067       case IcsExamining:
5068       case BeginningOfGame:
5069       case AnalyzeMode:
5070       case Training:
5071         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5072             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5073             /* User is moving for Black */
5074             if (WhiteOnMove(currentMove)) {
5075                 DisplayMoveError(_("It is White's turn"));
5076                 return ImpossibleMove;
5077             }
5078         } else {
5079             /* User is moving for White */
5080             if (!WhiteOnMove(currentMove)) {
5081                 DisplayMoveError(_("It is Black's turn"));
5082                 return ImpossibleMove;
5083             }
5084         }
5085         break;
5086
5087       case IcsPlayingBlack:
5088         /* User is moving for Black */
5089         if (WhiteOnMove(currentMove)) {
5090             if (!appData.premove) {
5091                 DisplayMoveError(_("It is White's turn"));
5092             } else if (toX >= 0 && toY >= 0) {
5093                 premoveToX = toX;
5094                 premoveToY = toY;
5095                 premoveFromX = fromX;
5096                 premoveFromY = fromY;
5097                 premovePromoChar = promoChar;
5098                 gotPremove = 1;
5099                 if (appData.debugMode) 
5100                     fprintf(debugFP, "Got premove: fromX %d,"
5101                             "fromY %d, toX %d, toY %d\n",
5102                             fromX, fromY, toX, toY);
5103             }
5104             return ImpossibleMove;
5105         }
5106         break;
5107
5108       case IcsPlayingWhite:
5109         /* User is moving for White */
5110         if (!WhiteOnMove(currentMove)) {
5111             if (!appData.premove) {
5112                 DisplayMoveError(_("It is Black's turn"));
5113             } else if (toX >= 0 && toY >= 0) {
5114                 premoveToX = toX;
5115                 premoveToY = toY;
5116                 premoveFromX = fromX;
5117                 premoveFromY = fromY;
5118                 premovePromoChar = promoChar;
5119                 gotPremove = 1;
5120                 if (appData.debugMode) 
5121                     fprintf(debugFP, "Got premove: fromX %d,"
5122                             "fromY %d, toX %d, toY %d\n",
5123                             fromX, fromY, toX, toY);
5124             }
5125             return ImpossibleMove;
5126         }
5127         break;
5128
5129       default:
5130         break;
5131
5132       case EditPosition:
5133         /* EditPosition, empty square, or different color piece;
5134            click-click move is possible */
5135         if (toX == -2 || toY == -2) {
5136             boards[0][fromY][fromX] = EmptySquare;
5137             return AmbiguousMove;
5138         } else if (toX >= 0 && toY >= 0) {
5139             boards[0][toY][toX] = boards[0][fromY][fromX];
5140             boards[0][fromY][fromX] = EmptySquare;
5141             return AmbiguousMove;
5142         }
5143         return ImpossibleMove;
5144     }
5145
5146     /* [HGM] If move started in holdings, it means a drop */
5147     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5148          if( pup != EmptySquare ) return ImpossibleMove;
5149          if(appData.testLegality) {
5150              /* it would be more logical if LegalityTest() also figured out
5151               * which drops are legal. For now we forbid pawns on back rank.
5152               * Shogi is on its own here...
5153               */
5154              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5155                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5156                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5157          }
5158          return WhiteDrop; /* Not needed to specify white or black yet */
5159     }
5160
5161     userOfferedDraw = FALSE;
5162         
5163     /* [HGM] always test for legality, to get promotion info */
5164     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5165                           epStatus[currentMove], castlingRights[currentMove],
5166                                          fromY, fromX, toY, toX, promoChar);
5167     /* [HGM] but possibly ignore an IllegalMove result */
5168     if (appData.testLegality) {
5169         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5170             DisplayMoveError(_("Illegal move"));
5171             return ImpossibleMove;
5172         }
5173     }
5174 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5175     return moveType;
5176     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5177        function is made into one that returns an OK move type if FinishMove
5178        should be called. This to give the calling driver routine the
5179        opportunity to finish the userMove input with a promotion popup,
5180        without bothering the user with this for invalid or illegal moves */
5181
5182 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5183 }
5184
5185 /* Common tail of UserMoveEvent and DropMenuEvent */
5186 int
5187 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5188      ChessMove moveType;
5189      int fromX, fromY, toX, toY;
5190      /*char*/int promoChar;
5191 {
5192     char *bookHit = 0;
5193 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5194     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5195         // [HGM] superchess: suppress promotions to non-available piece
5196         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5197         if(WhiteOnMove(currentMove)) {
5198             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5199         } else {
5200             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5201         }
5202     }
5203
5204     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5205        move type in caller when we know the move is a legal promotion */
5206     if(moveType == NormalMove && promoChar)
5207         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5208 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5209     /* [HGM] convert drag-and-drop piece drops to standard form */
5210     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5211          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5212            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5213                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5214 //         fromX = boards[currentMove][fromY][fromX];
5215            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5216            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5217            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5218            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5219          fromY = DROP_RANK;
5220     }
5221
5222     /* [HGM] <popupFix> The following if has been moved here from
5223        UserMoveEvent(). Because it seemed to belon here (why not allow
5224        piece drops in training games?), and because it can only be
5225        performed after it is known to what we promote. */
5226     if (gameMode == Training) {
5227       /* compare the move played on the board to the next move in the
5228        * game. If they match, display the move and the opponent's response. 
5229        * If they don't match, display an error message.
5230        */
5231       int saveAnimate;
5232       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5233       CopyBoard(testBoard, boards[currentMove]);
5234       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5235
5236       if (CompareBoards(testBoard, boards[currentMove+1])) {
5237         ForwardInner(currentMove+1);
5238
5239         /* Autoplay the opponent's response.
5240          * if appData.animate was TRUE when Training mode was entered,
5241          * the response will be animated.
5242          */
5243         saveAnimate = appData.animate;
5244         appData.animate = animateTraining;
5245         ForwardInner(currentMove+1);
5246         appData.animate = saveAnimate;
5247
5248         /* check for the end of the game */
5249         if (currentMove >= forwardMostMove) {
5250           gameMode = PlayFromGameFile;
5251           ModeHighlight();
5252           SetTrainingModeOff();
5253           DisplayInformation(_("End of game"));
5254         }
5255       } else {
5256         DisplayError(_("Incorrect move"), 0);
5257       }
5258       return 1;
5259     }
5260
5261   /* Ok, now we know that the move is good, so we can kill
5262      the previous line in Analysis Mode */
5263   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5264     forwardMostMove = currentMove;
5265   }
5266
5267   /* If we need the chess program but it's dead, restart it */
5268   ResurrectChessProgram();
5269
5270   /* A user move restarts a paused game*/
5271   if (pausing)
5272     PauseEvent();
5273
5274   thinkOutput[0] = NULLCHAR;
5275
5276   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5277
5278   if (gameMode == BeginningOfGame) {
5279     if (appData.noChessProgram) {
5280       gameMode = EditGame;
5281       SetGameInfo();
5282     } else {
5283       char buf[MSG_SIZ];
5284       gameMode = MachinePlaysBlack;
5285       StartClocks();
5286       SetGameInfo();
5287       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5288       DisplayTitle(buf);
5289       if (first.sendName) {
5290         sprintf(buf, "name %s\n", gameInfo.white);
5291         SendToProgram(buf, &first);
5292       }
5293       StartClocks();
5294     }
5295     ModeHighlight();
5296   }
5297 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5298   /* Relay move to ICS or chess engine */
5299   if (appData.icsActive) {
5300     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5301         gameMode == IcsExamining) {
5302       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5303       ics_user_moved = 1;
5304     }
5305   } else {
5306     if (first.sendTime && (gameMode == BeginningOfGame ||
5307                            gameMode == MachinePlaysWhite ||
5308                            gameMode == MachinePlaysBlack)) {
5309       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5310     }
5311     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5312          // [HGM] book: if program might be playing, let it use book
5313         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5314         first.maybeThinking = TRUE;
5315     } else SendMoveToProgram(forwardMostMove-1, &first);
5316     if (currentMove == cmailOldMove + 1) {
5317       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5318     }
5319   }
5320
5321   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5322
5323   switch (gameMode) {
5324   case EditGame:
5325     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5326                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5327     case MT_NONE:
5328     case MT_CHECK:
5329       break;
5330     case MT_CHECKMATE:
5331     case MT_STAINMATE:
5332       if (WhiteOnMove(currentMove)) {
5333         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5334       } else {
5335         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5336       }
5337       break;
5338     case MT_STALEMATE:
5339       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5340       break;
5341     }
5342     break;
5343     
5344   case MachinePlaysBlack:
5345   case MachinePlaysWhite:
5346     /* disable certain menu options while machine is thinking */
5347     SetMachineThinkingEnables();
5348     break;
5349
5350   default:
5351     break;
5352   }
5353
5354   if(bookHit) { // [HGM] book: simulate book reply
5355         static char bookMove[MSG_SIZ]; // a bit generous?
5356
5357         programStats.nodes = programStats.depth = programStats.time = 
5358         programStats.score = programStats.got_only_move = 0;
5359         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5360
5361         strcpy(bookMove, "move ");
5362         strcat(bookMove, bookHit);
5363         HandleMachineMove(bookMove, &first);
5364   }
5365   return 1;
5366 }
5367
5368 void
5369 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5370      int fromX, fromY, toX, toY;
5371      int promoChar;
5372 {
5373     /* [HGM] This routine was added to allow calling of its two logical
5374        parts from other modules in the old way. Before, UserMoveEvent()
5375        automatically called FinishMove() if the move was OK, and returned
5376        otherwise. I separated the two, in order to make it possible to
5377        slip a promotion popup in between. But that it always needs two
5378        calls, to the first part, (now called UserMoveTest() ), and to
5379        FinishMove if the first part succeeded. Calls that do not need
5380        to do anything in between, can call this routine the old way. 
5381     */
5382     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5383 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5384     if(moveType == AmbiguousMove)
5385         DrawPosition(FALSE, boards[currentMove]);
5386     else if(moveType != ImpossibleMove && moveType != Comment)
5387         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5388 }
5389
5390 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5391 {
5392 //    char * hint = lastHint;
5393     FrontEndProgramStats stats;
5394
5395     stats.which = cps == &first ? 0 : 1;
5396     stats.depth = cpstats->depth;
5397     stats.nodes = cpstats->nodes;
5398     stats.score = cpstats->score;
5399     stats.time = cpstats->time;
5400     stats.pv = cpstats->movelist;
5401     stats.hint = lastHint;
5402     stats.an_move_index = 0;
5403     stats.an_move_count = 0;
5404
5405     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5406         stats.hint = cpstats->move_name;
5407         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5408         stats.an_move_count = cpstats->nr_moves;
5409     }
5410
5411     SetProgramStats( &stats );
5412 }
5413
5414 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5415 {   // [HGM] book: this routine intercepts moves to simulate book replies
5416     char *bookHit = NULL;
5417
5418     //first determine if the incoming move brings opponent into his book
5419     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5420         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5421     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5422     if(bookHit != NULL && !cps->bookSuspend) {
5423         // make sure opponent is not going to reply after receiving move to book position
5424         SendToProgram("force\n", cps);
5425         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5426     }
5427     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5428     // now arrange restart after book miss
5429     if(bookHit) {
5430         // after a book hit we never send 'go', and the code after the call to this routine
5431         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5432         char buf[MSG_SIZ];
5433         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5434         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5435         SendToProgram(buf, cps);
5436         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5437     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5438         SendToProgram("go\n", cps);
5439         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5440     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5441         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5442             SendToProgram("go\n", cps); 
5443         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5444     }
5445     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5446 }
5447
5448 char *savedMessage;
5449 ChessProgramState *savedState;
5450 void DeferredBookMove(void)
5451 {
5452         if(savedState->lastPing != savedState->lastPong)
5453                     ScheduleDelayedEvent(DeferredBookMove, 10);
5454         else
5455         HandleMachineMove(savedMessage, savedState);
5456 }
5457
5458 void
5459 HandleMachineMove(message, cps)
5460      char *message;
5461      ChessProgramState *cps;
5462 {
5463     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5464     char realname[MSG_SIZ];
5465     int fromX, fromY, toX, toY;
5466     ChessMove moveType;
5467     char promoChar;
5468     char *p;
5469     int machineWhite;
5470     char *bookHit;
5471
5472 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5473     /*
5474      * Kludge to ignore BEL characters
5475      */
5476     while (*message == '\007') message++;
5477
5478     /*
5479      * [HGM] engine debug message: ignore lines starting with '#' character
5480      */
5481     if(cps->debug && *message == '#') return;
5482
5483     /*
5484      * Look for book output
5485      */
5486     if (cps == &first && bookRequested) {
5487         if (message[0] == '\t' || message[0] == ' ') {
5488             /* Part of the book output is here; append it */
5489             strcat(bookOutput, message);
5490             strcat(bookOutput, "  \n");
5491             return;
5492         } else if (bookOutput[0] != NULLCHAR) {
5493             /* All of book output has arrived; display it */
5494             char *p = bookOutput;
5495             while (*p != NULLCHAR) {
5496                 if (*p == '\t') *p = ' ';
5497                 p++;
5498             }
5499             DisplayInformation(bookOutput);
5500             bookRequested = FALSE;
5501             /* Fall through to parse the current output */
5502         }
5503     }
5504
5505     /*
5506      * Look for machine move.
5507      */
5508     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5509         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5510     {
5511         /* This method is only useful on engines that support ping */
5512         if (cps->lastPing != cps->lastPong) {
5513           if (gameMode == BeginningOfGame) {
5514             /* Extra move from before last new; ignore */
5515             if (appData.debugMode) {
5516                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5517             }
5518           } else {
5519             if (appData.debugMode) {
5520                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5521                         cps->which, gameMode);
5522             }
5523
5524             SendToProgram("undo\n", cps);
5525           }
5526           return;
5527         }
5528
5529         switch (gameMode) {
5530           case BeginningOfGame:
5531             /* Extra move from before last reset; ignore */
5532             if (appData.debugMode) {
5533                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5534             }
5535             return;
5536
5537           case EndOfGame:
5538           case IcsIdle:
5539           default:
5540             /* Extra move after we tried to stop.  The mode test is
5541                not a reliable way of detecting this problem, but it's
5542                the best we can do on engines that don't support ping.
5543             */
5544             if (appData.debugMode) {
5545                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5546                         cps->which, gameMode);
5547             }
5548             SendToProgram("undo\n", cps);
5549             return;
5550
5551           case MachinePlaysWhite:
5552           case IcsPlayingWhite:
5553             machineWhite = TRUE;
5554             break;
5555
5556           case MachinePlaysBlack:
5557           case IcsPlayingBlack:
5558             machineWhite = FALSE;
5559             break;
5560
5561           case TwoMachinesPlay:
5562             machineWhite = (cps->twoMachinesColor[0] == 'w');
5563             break;
5564         }
5565         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5566             if (appData.debugMode) {
5567                 fprintf(debugFP,
5568                         "Ignoring move out of turn by %s, gameMode %d"
5569                         ", forwardMost %d\n",
5570                         cps->which, gameMode, forwardMostMove);
5571             }
5572             return;
5573         }
5574
5575     if (appData.debugMode) { int f = forwardMostMove;
5576         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5577                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5578     }
5579         if(cps->alphaRank) AlphaRank(machineMove, 4);
5580         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5581                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5582             /* Machine move could not be parsed; ignore it. */
5583             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5584                     machineMove, cps->which);
5585             DisplayError(buf1, 0);
5586             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5587                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5588             if (gameMode == TwoMachinesPlay) {
5589               GameEnds(machineWhite ? BlackWins : WhiteWins,
5590                        buf1, GE_XBOARD);
5591             }
5592             return;
5593         }
5594
5595         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5596         /* So we have to redo legality test with true e.p. status here,  */
5597         /* to make sure an illegal e.p. capture does not slip through,   */
5598         /* to cause a forfeit on a justified illegal-move complaint      */
5599         /* of the opponent.                                              */
5600         if( gameMode==TwoMachinesPlay && appData.testLegality
5601             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5602                                                               ) {
5603            ChessMove moveType;
5604            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5605                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5606                              fromY, fromX, toY, toX, promoChar);
5607             if (appData.debugMode) {
5608                 int i;
5609                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5610                     castlingRights[forwardMostMove][i], castlingRank[i]);
5611                 fprintf(debugFP, "castling rights\n");
5612             }
5613             if(moveType == IllegalMove) {
5614                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5615                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5616                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5617                            buf1, GE_XBOARD);
5618                 return;
5619            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5620            /* [HGM] Kludge to handle engines that send FRC-style castling
5621               when they shouldn't (like TSCP-Gothic) */
5622            switch(moveType) {
5623              case WhiteASideCastleFR:
5624              case BlackASideCastleFR:
5625                toX+=2;
5626                currentMoveString[2]++;
5627                break;
5628              case WhiteHSideCastleFR:
5629              case BlackHSideCastleFR:
5630                toX--;
5631                currentMoveString[2]--;
5632                break;
5633              default: ; // nothing to do, but suppresses warning of pedantic compilers
5634            }
5635         }
5636         hintRequested = FALSE;
5637         lastHint[0] = NULLCHAR;
5638         bookRequested = FALSE;
5639         /* Program may be pondering now */
5640         cps->maybeThinking = TRUE;
5641         if (cps->sendTime == 2) cps->sendTime = 1;
5642         if (cps->offeredDraw) cps->offeredDraw--;
5643
5644 #if ZIPPY
5645         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5646             first.initDone) {
5647           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5648           ics_user_moved = 1;
5649           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5650                 char buf[3*MSG_SIZ];
5651
5652                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5653                         programStats.score / 100.,
5654                         programStats.depth,
5655                         programStats.time / 100.,
5656                         (unsigned int)programStats.nodes,
5657                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5658                         programStats.movelist);
5659                 SendToICS(buf);
5660 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5661           }
5662         }
5663 #endif
5664         /* currentMoveString is set as a side-effect of ParseOneMove */
5665         strcpy(machineMove, currentMoveString);
5666         strcat(machineMove, "\n");
5667         strcpy(moveList[forwardMostMove], machineMove);
5668
5669         /* [AS] Save move info and clear stats for next move */
5670         pvInfoList[ forwardMostMove ].score = programStats.score;
5671         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5672         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5673         ClearProgramStats();
5674         thinkOutput[0] = NULLCHAR;
5675         hiddenThinkOutputState = 0;
5676
5677         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5678
5679         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5680         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5681             int count = 0;
5682
5683             while( count < adjudicateLossPlies ) {
5684                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5685
5686                 if( count & 1 ) {
5687                     score = -score; /* Flip score for winning side */
5688                 }
5689
5690                 if( score > adjudicateLossThreshold ) {
5691                     break;
5692                 }
5693
5694                 count++;
5695             }
5696
5697             if( count >= adjudicateLossPlies ) {
5698                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5699
5700                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5701                     "Xboard adjudication", 
5702                     GE_XBOARD );
5703
5704                 return;
5705             }
5706         }
5707
5708         if( gameMode == TwoMachinesPlay ) {
5709           // [HGM] some adjudications useful with buggy engines
5710             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5711           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5712
5713
5714             if( appData.testLegality )
5715             {   /* [HGM] Some more adjudications for obstinate engines */
5716                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5717                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5718                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5719                 static int moveCount = 6;
5720                 ChessMove result;
5721                 char *reason = NULL;
5722
5723                 /* Count what is on board. */
5724                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5725                 {   ChessSquare p = boards[forwardMostMove][i][j];
5726                     int m=i;
5727
5728                     switch((int) p)
5729                     {   /* count B,N,R and other of each side */
5730                         case WhiteKing:
5731                         case BlackKing:
5732                              NrK++; break; // [HGM] atomic: count Kings
5733                         case WhiteKnight:
5734                              NrWN++; break;
5735                         case WhiteBishop:
5736                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5737                              bishopsColor |= 1 << ((i^j)&1);
5738                              NrWB++; break;
5739                         case BlackKnight:
5740                              NrBN++; break;
5741                         case BlackBishop:
5742                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5743                              bishopsColor |= 1 << ((i^j)&1);
5744                              NrBB++; break;
5745                         case WhiteRook:
5746                              NrWR++; break;
5747                         case BlackRook:
5748                              NrBR++; break;
5749                         case WhiteQueen:
5750                              NrWQ++; break;
5751                         case BlackQueen:
5752                              NrBQ++; break;
5753                         case EmptySquare: 
5754                              break;
5755                         case BlackPawn:
5756                              m = 7-i;
5757                         case WhitePawn:
5758                              PawnAdvance += m; NrPawns++;
5759                     }
5760                     NrPieces += (p != EmptySquare);
5761                     NrW += ((int)p < (int)BlackPawn);
5762                     if(gameInfo.variant == VariantXiangqi && 
5763                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5764                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5765                         NrW -= ((int)p < (int)BlackPawn);
5766                     }
5767                 }
5768
5769                 /* Some material-based adjudications that have to be made before stalemate test */
5770                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5771                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5772                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5773                      if(appData.checkMates) {
5774                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5775                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5776                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5777                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5778                          return;
5779                      }
5780                 }
5781
5782                 /* Bare King in Shatranj (loses) or Losers (wins) */
5783                 if( NrW == 1 || NrPieces - NrW == 1) {
5784                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5785                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5786                      if(appData.checkMates) {
5787                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5788                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5789                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5790                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5791                          return;
5792                      }
5793                   } else
5794                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5795                   {    /* bare King */
5796                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5797                         if(appData.checkMates) {
5798                             /* but only adjudicate if adjudication enabled */
5799                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5800                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5801                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5802                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5803                             return;
5804                         }
5805                   }
5806                 } else bare = 1;
5807
5808
5809             // don't wait for engine to announce game end if we can judge ourselves
5810             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5811                                        castlingRights[forwardMostMove]) ) {
5812               case MT_CHECK:
5813                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5814                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5815                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5816                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5817                             checkCnt++;
5818                         if(checkCnt >= 2) {
5819                             reason = "Xboard adjudication: 3rd check";
5820                             epStatus[forwardMostMove] = EP_CHECKMATE;
5821                             break;
5822                         }
5823                     }
5824                 }
5825               case MT_NONE:
5826               default:
5827                 break;
5828               case MT_STALEMATE:
5829               case MT_STAINMATE:
5830                 reason = "Xboard adjudication: Stalemate";
5831                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5832                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5833                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5834                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5835                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5836                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5837                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5838                                                                         EP_CHECKMATE : EP_WINS);
5839                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5840                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5841                 }
5842                 break;
5843               case MT_CHECKMATE:
5844                 reason = "Xboard adjudication: Checkmate";
5845                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5846                 break;
5847             }
5848
5849                 switch(i = epStatus[forwardMostMove]) {
5850                     case EP_STALEMATE:
5851                         result = GameIsDrawn; break;
5852                     case EP_CHECKMATE:
5853                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5854                     case EP_WINS:
5855                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5856                     default:
5857                         result = (ChessMove) 0;
5858                 }
5859                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5860                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5861                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5862                     GameEnds( result, reason, GE_XBOARD );
5863                     return;
5864                 }
5865
5866                 /* Next absolutely insufficient mating material. */
5867                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5868                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5869                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5870                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5871                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5872
5873                      /* always flag draws, for judging claims */
5874                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5875
5876                      if(appData.materialDraws) {
5877                          /* but only adjudicate them if adjudication enabled */
5878                          SendToProgram("force\n", cps->other); // suppress reply
5879                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5880                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5881                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5882                          return;
5883                      }
5884                 }
5885
5886                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5887                 if(NrPieces == 4 && 
5888                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5889                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5890                    || NrWN==2 || NrBN==2     /* KNNK */
5891                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5892                   ) ) {
5893                      if(--moveCount < 0 && appData.trivialDraws)
5894                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5895                           SendToProgram("force\n", cps->other); // suppress reply
5896                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5897                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5898                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5899                           return;
5900                      }
5901                 } else moveCount = 6;
5902             }
5903           }
5904           
5905           if (appData.debugMode) { int i;
5906             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5907                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5908                     appData.drawRepeats);
5909             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5910               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5911             
5912           }
5913
5914                 /* Check for rep-draws */
5915                 count = 0;
5916                 for(k = forwardMostMove-2;
5917                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5918                         epStatus[k] < EP_UNKNOWN &&
5919                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5920                     k-=2)
5921                 {   int rights=0;
5922                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5923                         /* compare castling rights */
5924                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5925                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5926                                 rights++; /* King lost rights, while rook still had them */
5927                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5928                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5929                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5930                                    rights++; /* but at least one rook lost them */
5931                         }
5932                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5933                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5934                                 rights++; 
5935                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5936                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5937                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5938                                    rights++;
5939                         }
5940                         if( rights == 0 && ++count > appData.drawRepeats-2
5941                             && appData.drawRepeats > 1) {
5942                              /* adjudicate after user-specified nr of repeats */
5943                              SendToProgram("force\n", cps->other); // suppress reply
5944                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5945                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5946                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5947                                 // [HGM] xiangqi: check for forbidden perpetuals
5948                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5949                                 for(m=forwardMostMove; m>k; m-=2) {
5950                                     if(MateTest(boards[m], PosFlags(m), 
5951                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5952                                         ourPerpetual = 0; // the current mover did not always check
5953                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5954                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5955                                         hisPerpetual = 0; // the opponent did not always check
5956                                 }
5957                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5958                                                                         ourPerpetual, hisPerpetual);
5959                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5960                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5961                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5962                                     return;
5963                                 }
5964                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5965                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5966                                 // Now check for perpetual chases
5967                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5968                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5969                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5970                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5971                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5972                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5973                                         return;
5974                                     }
5975                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5976                                         break; // Abort repetition-checking loop.
5977                                 }
5978                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5979                              }
5980                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5981                              return;
5982                         }
5983                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5984                              epStatus[forwardMostMove] = EP_REP_DRAW;
5985                     }
5986                 }
5987
5988                 /* Now we test for 50-move draws. Determine ply count */
5989                 count = forwardMostMove;
5990                 /* look for last irreversble move */
5991                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5992                     count--;
5993                 /* if we hit starting position, add initial plies */
5994                 if( count == backwardMostMove )
5995                     count -= initialRulePlies;
5996                 count = forwardMostMove - count; 
5997                 if( count >= 100)
5998                          epStatus[forwardMostMove] = EP_RULE_DRAW;
5999                          /* this is used to judge if draw claims are legal */
6000                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6001                          SendToProgram("force\n", cps->other); // suppress reply
6002                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6003                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6004                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6005                          return;
6006                 }
6007
6008                 /* if draw offer is pending, treat it as a draw claim
6009                  * when draw condition present, to allow engines a way to
6010                  * claim draws before making their move to avoid a race
6011                  * condition occurring after their move
6012                  */
6013                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6014                          char *p = NULL;
6015                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6016                              p = "Draw claim: 50-move rule";
6017                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6018                              p = "Draw claim: 3-fold repetition";
6019                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6020                              p = "Draw claim: insufficient mating material";
6021                          if( p != NULL ) {
6022                              SendToProgram("force\n", cps->other); // suppress reply
6023                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6024                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6025                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6026                              return;
6027                          }
6028                 }
6029
6030
6031                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6032                     SendToProgram("force\n", cps->other); // suppress reply
6033                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6034                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6035
6036                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6037
6038                     return;
6039                 }
6040         }
6041
6042         bookHit = NULL;
6043         if (gameMode == TwoMachinesPlay) {
6044             /* [HGM] relaying draw offers moved to after reception of move */
6045             /* and interpreting offer as claim if it brings draw condition */
6046             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6047                 SendToProgram("draw\n", cps->other);
6048             }
6049             if (cps->other->sendTime) {
6050                 SendTimeRemaining(cps->other,
6051                                   cps->other->twoMachinesColor[0] == 'w');
6052             }
6053             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6054             if (firstMove && !bookHit) {
6055                 firstMove = FALSE;
6056                 if (cps->other->useColors) {
6057                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6058                 }
6059                 SendToProgram("go\n", cps->other);
6060             }
6061             cps->other->maybeThinking = TRUE;
6062         }
6063
6064         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6065         
6066         if (!pausing && appData.ringBellAfterMoves) {
6067             RingBell();
6068         }
6069
6070         /* 
6071          * Reenable menu items that were disabled while
6072          * machine was thinking
6073          */
6074         if (gameMode != TwoMachinesPlay)
6075             SetUserThinkingEnables();
6076
6077         // [HGM] book: after book hit opponent has received move and is now in force mode
6078         // force the book reply into it, and then fake that it outputted this move by jumping
6079         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6080         if(bookHit) {
6081                 static char bookMove[MSG_SIZ]; // a bit generous?
6082
6083                 strcpy(bookMove, "move ");
6084                 strcat(bookMove, bookHit);
6085                 message = bookMove;
6086                 cps = cps->other;
6087                 programStats.nodes = programStats.depth = programStats.time = 
6088                 programStats.score = programStats.got_only_move = 0;
6089                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6090
6091                 if(cps->lastPing != cps->lastPong) {
6092                     savedMessage = message; // args for deferred call
6093                     savedState = cps;
6094                     ScheduleDelayedEvent(DeferredBookMove, 10);
6095                     return;
6096                 }
6097                 goto FakeBookMove;
6098         }
6099
6100         return;
6101     }
6102
6103     /* Set special modes for chess engines.  Later something general
6104      *  could be added here; for now there is just one kludge feature,
6105      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6106      *  when "xboard" is given as an interactive command.
6107      */
6108     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6109         cps->useSigint = FALSE;
6110         cps->useSigterm = FALSE;
6111     }
6112     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6113       ParseFeatures(message+8, cps);
6114       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6115     }
6116
6117     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6118      * want this, I was asked to put it in, and obliged.
6119      */
6120     if (!strncmp(message, "setboard ", 9)) {
6121         Board initial_position; int i;
6122
6123         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6124
6125         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6126             DisplayError(_("Bad FEN received from engine"), 0);
6127             return ;
6128         } else {
6129            Reset(FALSE, FALSE);
6130            CopyBoard(boards[0], initial_position);
6131            initialRulePlies = FENrulePlies;
6132            epStatus[0] = FENepStatus;
6133            for( i=0; i<nrCastlingRights; i++ )
6134                 castlingRights[0][i] = FENcastlingRights[i];
6135            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6136            else gameMode = MachinePlaysBlack;                 
6137            DrawPosition(FALSE, boards[currentMove]);
6138         }
6139         return;
6140     }
6141
6142     /*
6143      * Look for communication commands
6144      */
6145     if (!strncmp(message, "telluser ", 9)) {
6146         DisplayNote(message + 9);
6147         return;
6148     }
6149     if (!strncmp(message, "tellusererror ", 14)) {
6150         DisplayError(message + 14, 0);
6151         return;
6152     }
6153     if (!strncmp(message, "tellopponent ", 13)) {
6154       if (appData.icsActive) {
6155         if (loggedOn) {
6156           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6157           SendToICS(buf1);
6158         }
6159       } else {
6160         DisplayNote(message + 13);
6161       }
6162       return;
6163     }
6164     if (!strncmp(message, "tellothers ", 11)) {
6165       if (appData.icsActive) {
6166         if (loggedOn) {
6167           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6168           SendToICS(buf1);
6169         }
6170       }
6171       return;
6172     }
6173     if (!strncmp(message, "tellall ", 8)) {
6174       if (appData.icsActive) {
6175         if (loggedOn) {
6176           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6177           SendToICS(buf1);
6178         }
6179       } else {
6180         DisplayNote(message + 8);
6181       }
6182       return;
6183     }
6184     if (strncmp(message, "warning", 7) == 0) {
6185         /* Undocumented feature, use tellusererror in new code */
6186         DisplayError(message, 0);
6187         return;
6188     }
6189     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6190         strcpy(realname, cps->tidy);
6191         strcat(realname, " query");
6192         AskQuestion(realname, buf2, buf1, cps->pr);
6193         return;
6194     }
6195     /* Commands from the engine directly to ICS.  We don't allow these to be 
6196      *  sent until we are logged on. Crafty kibitzes have been known to 
6197      *  interfere with the login process.
6198      */
6199     if (loggedOn) {
6200         if (!strncmp(message, "tellics ", 8)) {
6201             SendToICS(message + 8);
6202             SendToICS("\n");
6203             return;
6204         }
6205         if (!strncmp(message, "tellicsnoalias ", 15)) {
6206             SendToICS(ics_prefix);
6207             SendToICS(message + 15);
6208             SendToICS("\n");
6209             return;
6210         }
6211         /* The following are for backward compatibility only */
6212         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6213             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6214             SendToICS(ics_prefix);
6215             SendToICS(message);
6216             SendToICS("\n");
6217             return;
6218         }
6219     }
6220     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6221         return;
6222     }
6223     /*
6224      * If the move is illegal, cancel it and redraw the board.
6225      * Also deal with other error cases.  Matching is rather loose
6226      * here to accommodate engines written before the spec.
6227      */
6228     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6229         strncmp(message, "Error", 5) == 0) {
6230         if (StrStr(message, "name") || 
6231             StrStr(message, "rating") || StrStr(message, "?") ||
6232             StrStr(message, "result") || StrStr(message, "board") ||
6233             StrStr(message, "bk") || StrStr(message, "computer") ||
6234             StrStr(message, "variant") || StrStr(message, "hint") ||
6235             StrStr(message, "random") || StrStr(message, "depth") ||
6236             StrStr(message, "accepted")) {
6237             return;
6238         }
6239         if (StrStr(message, "protover")) {
6240           /* Program is responding to input, so it's apparently done
6241              initializing, and this error message indicates it is
6242              protocol version 1.  So we don't need to wait any longer
6243              for it to initialize and send feature commands. */
6244           FeatureDone(cps, 1);
6245           cps->protocolVersion = 1;
6246           return;
6247         }
6248         cps->maybeThinking = FALSE;
6249
6250         if (StrStr(message, "draw")) {
6251             /* Program doesn't have "draw" command */
6252             cps->sendDrawOffers = 0;
6253             return;
6254         }
6255         if (cps->sendTime != 1 &&
6256             (StrStr(message, "time") || StrStr(message, "otim"))) {
6257           /* Program apparently doesn't have "time" or "otim" command */
6258           cps->sendTime = 0;
6259           return;
6260         }
6261         if (StrStr(message, "analyze")) {
6262             cps->analysisSupport = FALSE;
6263             cps->analyzing = FALSE;
6264             Reset(FALSE, TRUE);
6265             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6266             DisplayError(buf2, 0);
6267             return;
6268         }
6269         if (StrStr(message, "(no matching move)st")) {
6270           /* Special kludge for GNU Chess 4 only */
6271           cps->stKludge = TRUE;
6272           SendTimeControl(cps, movesPerSession, timeControl,
6273                           timeIncrement, appData.searchDepth,
6274                           searchTime);
6275           return;
6276         }
6277         if (StrStr(message, "(no matching move)sd")) {
6278           /* Special kludge for GNU Chess 4 only */
6279           cps->sdKludge = TRUE;
6280           SendTimeControl(cps, movesPerSession, timeControl,
6281                           timeIncrement, appData.searchDepth,
6282                           searchTime);
6283           return;
6284         }
6285         if (!StrStr(message, "llegal")) {
6286             return;
6287         }
6288         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6289             gameMode == IcsIdle) return;
6290         if (forwardMostMove <= backwardMostMove) return;
6291         if (pausing) PauseEvent();
6292       if(appData.forceIllegal) {
6293             // [HGM] illegal: machine refused move; force position after move into it
6294           SendToProgram("force\n", cps);
6295           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6296                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6297                 // when black is to move, while there might be nothing on a2 or black
6298                 // might already have the move. So send the board as if white has the move.
6299                 // But first we must change the stm of the engine, as it refused the last move
6300                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6301                 if(WhiteOnMove(forwardMostMove)) {
6302                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6303                     SendBoard(cps, forwardMostMove); // kludgeless board
6304                 } else {
6305                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6306                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6307                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6308                 }
6309           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6310             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6311                  gameMode == TwoMachinesPlay)
6312               SendToProgram("go\n", cps);
6313             return;
6314       } else
6315         if (gameMode == PlayFromGameFile) {
6316             /* Stop reading this game file */
6317             gameMode = EditGame;
6318             ModeHighlight();
6319         }
6320         currentMove = --forwardMostMove;
6321         DisplayMove(currentMove-1); /* before DisplayMoveError */
6322         SwitchClocks();
6323         DisplayBothClocks();
6324         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6325                 parseList[currentMove], cps->which);
6326         DisplayMoveError(buf1);
6327         DrawPosition(FALSE, boards[currentMove]);
6328
6329         /* [HGM] illegal-move claim should forfeit game when Xboard */
6330         /* only passes fully legal moves                            */
6331         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6332             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6333                                 "False illegal-move claim", GE_XBOARD );
6334         }
6335         return;
6336     }
6337     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6338         /* Program has a broken "time" command that
6339            outputs a string not ending in newline.
6340            Don't use it. */
6341         cps->sendTime = 0;
6342     }
6343     
6344     /*
6345      * If chess program startup fails, exit with an error message.
6346      * Attempts to recover here are futile.
6347      */
6348     if ((StrStr(message, "unknown host") != NULL)
6349         || (StrStr(message, "No remote directory") != NULL)
6350         || (StrStr(message, "not found") != NULL)
6351         || (StrStr(message, "No such file") != NULL)
6352         || (StrStr(message, "can't alloc") != NULL)
6353         || (StrStr(message, "Permission denied") != NULL)) {
6354
6355         cps->maybeThinking = FALSE;
6356         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6357                 cps->which, cps->program, cps->host, message);
6358         RemoveInputSource(cps->isr);
6359         DisplayFatalError(buf1, 0, 1);
6360         return;
6361     }
6362     
6363     /* 
6364      * Look for hint output
6365      */
6366     if (sscanf(message, "Hint: %s", buf1) == 1) {
6367         if (cps == &first && hintRequested) {
6368             hintRequested = FALSE;
6369             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6370                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6371                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6372                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6373                                     fromY, fromX, toY, toX, promoChar, buf1);
6374                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6375                 DisplayInformation(buf2);
6376             } else {
6377                 /* Hint move could not be parsed!? */
6378               snprintf(buf2, sizeof(buf2),
6379                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6380                         buf1, cps->which);
6381                 DisplayError(buf2, 0);
6382             }
6383         } else {
6384             strcpy(lastHint, buf1);
6385         }
6386         return;
6387     }
6388
6389     /*
6390      * Ignore other messages if game is not in progress
6391      */
6392     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6393         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6394
6395     /*
6396      * look for win, lose, draw, or draw offer
6397      */
6398     if (strncmp(message, "1-0", 3) == 0) {
6399         char *p, *q, *r = "";
6400         p = strchr(message, '{');
6401         if (p) {
6402             q = strchr(p, '}');
6403             if (q) {
6404                 *q = NULLCHAR;
6405                 r = p + 1;
6406             }
6407         }
6408         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6409         return;
6410     } else if (strncmp(message, "0-1", 3) == 0) {
6411         char *p, *q, *r = "";
6412         p = strchr(message, '{');
6413         if (p) {
6414             q = strchr(p, '}');
6415             if (q) {
6416                 *q = NULLCHAR;
6417                 r = p + 1;
6418             }
6419         }
6420         /* Kludge for Arasan 4.1 bug */
6421         if (strcmp(r, "Black resigns") == 0) {
6422             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6423             return;
6424         }
6425         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6426         return;
6427     } else if (strncmp(message, "1/2", 3) == 0) {
6428         char *p, *q, *r = "";
6429         p = strchr(message, '{');
6430         if (p) {
6431             q = strchr(p, '}');
6432             if (q) {
6433                 *q = NULLCHAR;
6434                 r = p + 1;
6435             }
6436         }
6437             
6438         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6439         return;
6440
6441     } else if (strncmp(message, "White resign", 12) == 0) {
6442         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6443         return;
6444     } else if (strncmp(message, "Black resign", 12) == 0) {
6445         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6446         return;
6447     } else if (strncmp(message, "White matches", 13) == 0 ||
6448                strncmp(message, "Black matches", 13) == 0   ) {
6449         /* [HGM] ignore GNUShogi noises */
6450         return;
6451     } else if (strncmp(message, "White", 5) == 0 &&
6452                message[5] != '(' &&
6453                StrStr(message, "Black") == NULL) {
6454         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6455         return;
6456     } else if (strncmp(message, "Black", 5) == 0 &&
6457                message[5] != '(') {
6458         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6459         return;
6460     } else if (strcmp(message, "resign") == 0 ||
6461                strcmp(message, "computer resigns") == 0) {
6462         switch (gameMode) {
6463           case MachinePlaysBlack:
6464           case IcsPlayingBlack:
6465             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6466             break;
6467           case MachinePlaysWhite:
6468           case IcsPlayingWhite:
6469             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6470             break;
6471           case TwoMachinesPlay:
6472             if (cps->twoMachinesColor[0] == 'w')
6473               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6474             else
6475               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6476             break;
6477           default:
6478             /* can't happen */
6479             break;
6480         }
6481         return;
6482     } else if (strncmp(message, "opponent mates", 14) == 0) {
6483         switch (gameMode) {
6484           case MachinePlaysBlack:
6485           case IcsPlayingBlack:
6486             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6487             break;
6488           case MachinePlaysWhite:
6489           case IcsPlayingWhite:
6490             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6491             break;
6492           case TwoMachinesPlay:
6493             if (cps->twoMachinesColor[0] == 'w')
6494               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6495             else
6496               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6497             break;
6498           default:
6499             /* can't happen */
6500             break;
6501         }
6502         return;
6503     } else if (strncmp(message, "computer mates", 14) == 0) {
6504         switch (gameMode) {
6505           case MachinePlaysBlack:
6506           case IcsPlayingBlack:
6507             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6508             break;
6509           case MachinePlaysWhite:
6510           case IcsPlayingWhite:
6511             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6512             break;
6513           case TwoMachinesPlay:
6514             if (cps->twoMachinesColor[0] == 'w')
6515               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6516             else
6517               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6518             break;
6519           default:
6520             /* can't happen */
6521             break;
6522         }
6523         return;
6524     } else if (strncmp(message, "checkmate", 9) == 0) {
6525         if (WhiteOnMove(forwardMostMove)) {
6526             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6527         } else {
6528             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6529         }
6530         return;
6531     } else if (strstr(message, "Draw") != NULL ||
6532                strstr(message, "game is a draw") != NULL) {
6533         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6534         return;
6535     } else if (strstr(message, "offer") != NULL &&
6536                strstr(message, "draw") != NULL) {
6537 #if ZIPPY
6538         if (appData.zippyPlay && first.initDone) {
6539             /* Relay offer to ICS */
6540             SendToICS(ics_prefix);
6541             SendToICS("draw\n");
6542         }
6543 #endif
6544         cps->offeredDraw = 2; /* valid until this engine moves twice */
6545         if (gameMode == TwoMachinesPlay) {
6546             if (cps->other->offeredDraw) {
6547                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6548             /* [HGM] in two-machine mode we delay relaying draw offer      */
6549             /* until after we also have move, to see if it is really claim */
6550             }
6551         } else if (gameMode == MachinePlaysWhite ||
6552                    gameMode == MachinePlaysBlack) {
6553           if (userOfferedDraw) {
6554             DisplayInformation(_("Machine accepts your draw offer"));
6555             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6556           } else {
6557             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6558           }
6559         }
6560     }
6561
6562     
6563     /*
6564      * Look for thinking output
6565      */
6566     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6567           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6568                                 ) {
6569         int plylev, mvleft, mvtot, curscore, time;
6570         char mvname[MOVE_LEN];
6571         u64 nodes; // [DM]
6572         char plyext;
6573         int ignore = FALSE;
6574         int prefixHint = FALSE;
6575         mvname[0] = NULLCHAR;
6576
6577         switch (gameMode) {
6578           case MachinePlaysBlack:
6579           case IcsPlayingBlack:
6580             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6581             break;
6582           case MachinePlaysWhite:
6583           case IcsPlayingWhite:
6584             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6585             break;
6586           case AnalyzeMode:
6587           case AnalyzeFile:
6588             break;
6589           case IcsObserving: /* [DM] icsEngineAnalyze */
6590             if (!appData.icsEngineAnalyze) ignore = TRUE;
6591             break;
6592           case TwoMachinesPlay:
6593             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6594                 ignore = TRUE;
6595             }
6596             break;
6597           default:
6598             ignore = TRUE;
6599             break;
6600         }
6601
6602         if (!ignore) {
6603             buf1[0] = NULLCHAR;
6604             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6605                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6606
6607                 if (plyext != ' ' && plyext != '\t') {
6608                     time *= 100;
6609                 }
6610
6611                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6612                 if( cps->scoreIsAbsolute && 
6613                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6614                 {
6615                     curscore = -curscore;
6616                 }
6617
6618
6619                 programStats.depth = plylev;
6620                 programStats.nodes = nodes;
6621                 programStats.time = time;
6622                 programStats.score = curscore;
6623                 programStats.got_only_move = 0;
6624
6625                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6626                         int ticklen;
6627
6628                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6629                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6630                         if(WhiteOnMove(forwardMostMove)) 
6631                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6632                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6633                 }
6634
6635                 /* Buffer overflow protection */
6636                 if (buf1[0] != NULLCHAR) {
6637                     if (strlen(buf1) >= sizeof(programStats.movelist)
6638                         && appData.debugMode) {
6639                         fprintf(debugFP,
6640                                 "PV is too long; using the first %d bytes.\n",
6641                                 sizeof(programStats.movelist) - 1);
6642                     }
6643
6644                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6645                 } else {
6646                     sprintf(programStats.movelist, " no PV\n");
6647                 }
6648
6649                 if (programStats.seen_stat) {
6650                     programStats.ok_to_send = 1;
6651                 }
6652
6653                 if (strchr(programStats.movelist, '(') != NULL) {
6654                     programStats.line_is_book = 1;
6655                     programStats.nr_moves = 0;
6656                     programStats.moves_left = 0;
6657                 } else {
6658                     programStats.line_is_book = 0;
6659                 }
6660
6661                 SendProgramStatsToFrontend( cps, &programStats );
6662
6663                 /* 
6664                     [AS] Protect the thinkOutput buffer from overflow... this
6665                     is only useful if buf1 hasn't overflowed first!
6666                 */
6667                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6668                         plylev, 
6669                         (gameMode == TwoMachinesPlay ?
6670                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6671                         ((double) curscore) / 100.0,
6672                         prefixHint ? lastHint : "",
6673                         prefixHint ? " " : "" );
6674
6675                 if( buf1[0] != NULLCHAR ) {
6676                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6677
6678                     if( strlen(buf1) > max_len ) {
6679                         if( appData.debugMode) {
6680                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6681                         }
6682                         buf1[max_len+1] = '\0';
6683                     }
6684
6685                     strcat( thinkOutput, buf1 );
6686                 }
6687
6688                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6689                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6690                     DisplayMove(currentMove - 1);
6691                     DisplayAnalysis();
6692                 }
6693                 return;
6694
6695             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6696                 /* crafty (9.25+) says "(only move) <move>"
6697                  * if there is only 1 legal move
6698                  */
6699                 sscanf(p, "(only move) %s", buf1);
6700                 sprintf(thinkOutput, "%s (only move)", buf1);
6701                 sprintf(programStats.movelist, "%s (only move)", buf1);
6702                 programStats.depth = 1;
6703                 programStats.nr_moves = 1;
6704                 programStats.moves_left = 1;
6705                 programStats.nodes = 1;
6706                 programStats.time = 1;
6707                 programStats.got_only_move = 1;
6708
6709                 /* Not really, but we also use this member to
6710                    mean "line isn't going to change" (Crafty
6711                    isn't searching, so stats won't change) */
6712                 programStats.line_is_book = 1;
6713
6714                 SendProgramStatsToFrontend( cps, &programStats );
6715                 
6716                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6717                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6718                     DisplayMove(currentMove - 1);
6719                     DisplayAnalysis();
6720                 }
6721                 return;
6722             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6723                               &time, &nodes, &plylev, &mvleft,
6724                               &mvtot, mvname) >= 5) {
6725                 /* The stat01: line is from Crafty (9.29+) in response
6726                    to the "." command */
6727                 programStats.seen_stat = 1;
6728                 cps->maybeThinking = TRUE;
6729
6730                 if (programStats.got_only_move || !appData.periodicUpdates)
6731                   return;
6732
6733                 programStats.depth = plylev;
6734                 programStats.time = time;
6735                 programStats.nodes = nodes;
6736                 programStats.moves_left = mvleft;
6737                 programStats.nr_moves = mvtot;
6738                 strcpy(programStats.move_name, mvname);
6739                 programStats.ok_to_send = 1;
6740                 programStats.movelist[0] = '\0';
6741
6742                 SendProgramStatsToFrontend( cps, &programStats );
6743
6744                 DisplayAnalysis();
6745                 return;
6746
6747             } else if (strncmp(message,"++",2) == 0) {
6748                 /* Crafty 9.29+ outputs this */
6749                 programStats.got_fail = 2;
6750                 return;
6751
6752             } else if (strncmp(message,"--",2) == 0) {
6753                 /* Crafty 9.29+ outputs this */
6754                 programStats.got_fail = 1;
6755                 return;
6756
6757             } else if (thinkOutput[0] != NULLCHAR &&
6758                        strncmp(message, "    ", 4) == 0) {
6759                 unsigned message_len;
6760
6761                 p = message;
6762                 while (*p && *p == ' ') p++;
6763
6764                 message_len = strlen( p );
6765
6766                 /* [AS] Avoid buffer overflow */
6767                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6768                     strcat(thinkOutput, " ");
6769                     strcat(thinkOutput, p);
6770                 }
6771
6772                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6773                     strcat(programStats.movelist, " ");
6774                     strcat(programStats.movelist, p);
6775                 }
6776
6777                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6778                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6779                     DisplayMove(currentMove - 1);
6780                     DisplayAnalysis();
6781                 }
6782                 return;
6783             }
6784         }
6785         else {
6786             buf1[0] = NULLCHAR;
6787
6788             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6789                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6790             {
6791                 ChessProgramStats cpstats;
6792
6793                 if (plyext != ' ' && plyext != '\t') {
6794                     time *= 100;
6795                 }
6796
6797                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6798                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6799                     curscore = -curscore;
6800                 }
6801
6802                 cpstats.depth = plylev;
6803                 cpstats.nodes = nodes;
6804                 cpstats.time = time;
6805                 cpstats.score = curscore;
6806                 cpstats.got_only_move = 0;
6807                 cpstats.movelist[0] = '\0';
6808
6809                 if (buf1[0] != NULLCHAR) {
6810                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6811                 }
6812
6813                 cpstats.ok_to_send = 0;
6814                 cpstats.line_is_book = 0;
6815                 cpstats.nr_moves = 0;
6816                 cpstats.moves_left = 0;
6817
6818                 SendProgramStatsToFrontend( cps, &cpstats );
6819             }
6820         }
6821     }
6822 }
6823
6824
6825 /* Parse a game score from the character string "game", and
6826    record it as the history of the current game.  The game
6827    score is NOT assumed to start from the standard position. 
6828    The display is not updated in any way.
6829    */
6830 void
6831 ParseGameHistory(game)
6832      char *game;
6833 {
6834     ChessMove moveType;
6835     int fromX, fromY, toX, toY, boardIndex;
6836     char promoChar;
6837     char *p, *q;
6838     char buf[MSG_SIZ];
6839
6840     if (appData.debugMode)
6841       fprintf(debugFP, "Parsing game history: %s\n", game);
6842
6843     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6844     gameInfo.site = StrSave(appData.icsHost);
6845     gameInfo.date = PGNDate();
6846     gameInfo.round = StrSave("-");
6847
6848     /* Parse out names of players */
6849     while (*game == ' ') game++;
6850     p = buf;
6851     while (*game != ' ') *p++ = *game++;
6852     *p = NULLCHAR;
6853     gameInfo.white = StrSave(buf);
6854     while (*game == ' ') game++;
6855     p = buf;
6856     while (*game != ' ' && *game != '\n') *p++ = *game++;
6857     *p = NULLCHAR;
6858     gameInfo.black = StrSave(buf);
6859
6860     /* Parse moves */
6861     boardIndex = blackPlaysFirst ? 1 : 0;
6862     yynewstr(game);
6863     for (;;) {
6864         yyboardindex = boardIndex;
6865         moveType = (ChessMove) yylex();
6866         switch (moveType) {
6867           case IllegalMove:             /* maybe suicide chess, etc. */
6868   if (appData.debugMode) {
6869     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6870     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6871     setbuf(debugFP, NULL);
6872   }
6873           case WhitePromotionChancellor:
6874           case BlackPromotionChancellor:
6875           case WhitePromotionArchbishop:
6876           case BlackPromotionArchbishop:
6877           case WhitePromotionQueen:
6878           case BlackPromotionQueen:
6879           case WhitePromotionRook:
6880           case BlackPromotionRook:
6881           case WhitePromotionBishop:
6882           case BlackPromotionBishop:
6883           case WhitePromotionKnight:
6884           case BlackPromotionKnight:
6885           case WhitePromotionKing:
6886           case BlackPromotionKing:
6887           case NormalMove:
6888           case WhiteCapturesEnPassant:
6889           case BlackCapturesEnPassant:
6890           case WhiteKingSideCastle:
6891           case WhiteQueenSideCastle:
6892           case BlackKingSideCastle:
6893           case BlackQueenSideCastle:
6894           case WhiteKingSideCastleWild:
6895           case WhiteQueenSideCastleWild:
6896           case BlackKingSideCastleWild:
6897           case BlackQueenSideCastleWild:
6898           /* PUSH Fabien */
6899           case WhiteHSideCastleFR:
6900           case WhiteASideCastleFR:
6901           case BlackHSideCastleFR:
6902           case BlackASideCastleFR:
6903           /* POP Fabien */
6904             fromX = currentMoveString[0] - AAA;
6905             fromY = currentMoveString[1] - ONE;
6906             toX = currentMoveString[2] - AAA;
6907             toY = currentMoveString[3] - ONE;
6908             promoChar = currentMoveString[4];
6909             break;
6910           case WhiteDrop:
6911           case BlackDrop:
6912             fromX = moveType == WhiteDrop ?
6913               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6914             (int) CharToPiece(ToLower(currentMoveString[0]));
6915             fromY = DROP_RANK;
6916             toX = currentMoveString[2] - AAA;
6917             toY = currentMoveString[3] - ONE;
6918             promoChar = NULLCHAR;
6919             break;
6920           case AmbiguousMove:
6921             /* bug? */
6922             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6923   if (appData.debugMode) {
6924     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6925     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6926     setbuf(debugFP, NULL);
6927   }
6928             DisplayError(buf, 0);
6929             return;
6930           case ImpossibleMove:
6931             /* bug? */
6932             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6933   if (appData.debugMode) {
6934     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6935     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6936     setbuf(debugFP, NULL);
6937   }
6938             DisplayError(buf, 0);
6939             return;
6940           case (ChessMove) 0:   /* end of file */
6941             if (boardIndex < backwardMostMove) {
6942                 /* Oops, gap.  How did that happen? */
6943                 DisplayError(_("Gap in move list"), 0);
6944                 return;
6945             }
6946             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6947             if (boardIndex > forwardMostMove) {
6948                 forwardMostMove = boardIndex;
6949             }
6950             return;
6951           case ElapsedTime:
6952             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6953                 strcat(parseList[boardIndex-1], " ");
6954                 strcat(parseList[boardIndex-1], yy_text);
6955             }
6956             continue;
6957           case Comment:
6958           case PGNTag:
6959           case NAG:
6960           default:
6961             /* ignore */
6962             continue;
6963           case WhiteWins:
6964           case BlackWins:
6965           case GameIsDrawn:
6966           case GameUnfinished:
6967             if (gameMode == IcsExamining) {
6968                 if (boardIndex < backwardMostMove) {
6969                     /* Oops, gap.  How did that happen? */
6970                     return;
6971                 }
6972                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6973                 return;
6974             }
6975             gameInfo.result = moveType;
6976             p = strchr(yy_text, '{');
6977             if (p == NULL) p = strchr(yy_text, '(');
6978             if (p == NULL) {
6979                 p = yy_text;
6980                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6981             } else {
6982                 q = strchr(p, *p == '{' ? '}' : ')');
6983                 if (q != NULL) *q = NULLCHAR;
6984                 p++;
6985             }
6986             gameInfo.resultDetails = StrSave(p);
6987             continue;
6988         }
6989         if (boardIndex >= forwardMostMove &&
6990             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6991             backwardMostMove = blackPlaysFirst ? 1 : 0;
6992             return;
6993         }
6994         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6995                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6996                                  parseList[boardIndex]);
6997         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6998         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6999         /* currentMoveString is set as a side-effect of yylex */
7000         strcpy(moveList[boardIndex], currentMoveString);
7001         strcat(moveList[boardIndex], "\n");
7002         boardIndex++;
7003         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7004                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7005         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7006                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7007           case MT_NONE:
7008           case MT_STALEMATE:
7009           default:
7010             break;
7011           case MT_CHECK:
7012             if(gameInfo.variant != VariantShogi)
7013                 strcat(parseList[boardIndex - 1], "+");
7014             break;
7015           case MT_CHECKMATE:
7016           case MT_STAINMATE:
7017             strcat(parseList[boardIndex - 1], "#");
7018             break;
7019         }
7020     }
7021 }
7022
7023
7024 /* Apply a move to the given board  */
7025 void
7026 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7027      int fromX, fromY, toX, toY;
7028      int promoChar;
7029      Board board;
7030      char *castling;
7031      char *ep;
7032 {
7033   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7034
7035     /* [HGM] compute & store e.p. status and castling rights for new position */
7036     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7037     { int i;
7038
7039       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7040       oldEP = *ep;
7041       *ep = EP_NONE;
7042
7043       if( board[toY][toX] != EmptySquare ) 
7044            *ep = EP_CAPTURE;  
7045
7046       if( board[fromY][fromX] == WhitePawn ) {
7047            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7048                *ep = EP_PAWN_MOVE;
7049            if( toY-fromY==2) {
7050                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7051                         gameInfo.variant != VariantBerolina || toX < fromX)
7052                       *ep = toX | berolina;
7053                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7054                         gameInfo.variant != VariantBerolina || toX > fromX) 
7055                       *ep = toX;
7056            }
7057       } else 
7058       if( board[fromY][fromX] == BlackPawn ) {
7059            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7060                *ep = EP_PAWN_MOVE; 
7061            if( toY-fromY== -2) {
7062                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7063                         gameInfo.variant != VariantBerolina || toX < fromX)
7064                       *ep = toX | berolina;
7065                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7066                         gameInfo.variant != VariantBerolina || toX > fromX) 
7067                       *ep = toX;
7068            }
7069        }
7070
7071        for(i=0; i<nrCastlingRights; i++) {
7072            if(castling[i] == fromX && castlingRank[i] == fromY ||
7073               castling[i] == toX   && castlingRank[i] == toY   
7074              ) castling[i] = -1; // revoke for moved or captured piece
7075        }
7076
7077     }
7078
7079   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7080   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7081        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7082          
7083   if (fromX == toX && fromY == toY) return;
7084
7085   if (fromY == DROP_RANK) {
7086         /* must be first */
7087         piece = board[toY][toX] = (ChessSquare) fromX;
7088   } else {
7089      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7090      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7091      if(gameInfo.variant == VariantKnightmate)
7092          king += (int) WhiteUnicorn - (int) WhiteKing;
7093
7094     /* Code added by Tord: */
7095     /* FRC castling assumed when king captures friendly rook. */
7096     if (board[fromY][fromX] == WhiteKing &&
7097              board[toY][toX] == WhiteRook) {
7098       board[fromY][fromX] = EmptySquare;
7099       board[toY][toX] = EmptySquare;
7100       if(toX > fromX) {
7101         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7102       } else {
7103         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7104       }
7105     } else if (board[fromY][fromX] == BlackKing &&
7106                board[toY][toX] == BlackRook) {
7107       board[fromY][fromX] = EmptySquare;
7108       board[toY][toX] = EmptySquare;
7109       if(toX > fromX) {
7110         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7111       } else {
7112         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7113       }
7114     /* End of code added by Tord */
7115
7116     } else if (board[fromY][fromX] == king
7117         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7118         && toY == fromY && toX > fromX+1) {
7119         board[fromY][fromX] = EmptySquare;
7120         board[toY][toX] = king;
7121         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7122         board[fromY][BOARD_RGHT-1] = EmptySquare;
7123     } else if (board[fromY][fromX] == king
7124         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7125                && toY == fromY && toX < fromX-1) {
7126         board[fromY][fromX] = EmptySquare;
7127         board[toY][toX] = king;
7128         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7129         board[fromY][BOARD_LEFT] = EmptySquare;
7130     } else if (board[fromY][fromX] == WhitePawn
7131                && toY == BOARD_HEIGHT-1
7132                && gameInfo.variant != VariantXiangqi
7133                ) {
7134         /* white pawn promotion */
7135         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7136         if (board[toY][toX] == EmptySquare) {
7137             board[toY][toX] = WhiteQueen;
7138         }
7139         if(gameInfo.variant==VariantBughouse ||
7140            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7141             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7142         board[fromY][fromX] = EmptySquare;
7143     } else if ((fromY == BOARD_HEIGHT-4)
7144                && (toX != fromX)
7145                && gameInfo.variant != VariantXiangqi
7146                && gameInfo.variant != VariantBerolina
7147                && (board[fromY][fromX] == WhitePawn)
7148                && (board[toY][toX] == EmptySquare)) {
7149         board[fromY][fromX] = EmptySquare;
7150         board[toY][toX] = WhitePawn;
7151         captured = board[toY - 1][toX];
7152         board[toY - 1][toX] = EmptySquare;
7153     } else if ((fromY == BOARD_HEIGHT-4)
7154                && (toX == fromX)
7155                && gameInfo.variant == VariantBerolina
7156                && (board[fromY][fromX] == WhitePawn)
7157                && (board[toY][toX] == EmptySquare)) {
7158         board[fromY][fromX] = EmptySquare;
7159         board[toY][toX] = WhitePawn;
7160         if(oldEP & EP_BEROLIN_A) {
7161                 captured = board[fromY][fromX-1];
7162                 board[fromY][fromX-1] = EmptySquare;
7163         }else{  captured = board[fromY][fromX+1];
7164                 board[fromY][fromX+1] = EmptySquare;
7165         }
7166     } else if (board[fromY][fromX] == king
7167         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7168                && toY == fromY && toX > fromX+1) {
7169         board[fromY][fromX] = EmptySquare;
7170         board[toY][toX] = king;
7171         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7172         board[fromY][BOARD_RGHT-1] = EmptySquare;
7173     } else if (board[fromY][fromX] == king
7174         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7175                && toY == fromY && toX < fromX-1) {
7176         board[fromY][fromX] = EmptySquare;
7177         board[toY][toX] = king;
7178         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7179         board[fromY][BOARD_LEFT] = EmptySquare;
7180     } else if (fromY == 7 && fromX == 3
7181                && board[fromY][fromX] == BlackKing
7182                && toY == 7 && toX == 5) {
7183         board[fromY][fromX] = EmptySquare;
7184         board[toY][toX] = BlackKing;
7185         board[fromY][7] = EmptySquare;
7186         board[toY][4] = BlackRook;
7187     } else if (fromY == 7 && fromX == 3
7188                && board[fromY][fromX] == BlackKing
7189                && toY == 7 && toX == 1) {
7190         board[fromY][fromX] = EmptySquare;
7191         board[toY][toX] = BlackKing;
7192         board[fromY][0] = EmptySquare;
7193         board[toY][2] = BlackRook;
7194     } else if (board[fromY][fromX] == BlackPawn
7195                && toY == 0
7196                && gameInfo.variant != VariantXiangqi
7197                ) {
7198         /* black pawn promotion */
7199         board[0][toX] = CharToPiece(ToLower(promoChar));
7200         if (board[0][toX] == EmptySquare) {
7201             board[0][toX] = BlackQueen;
7202         }
7203         if(gameInfo.variant==VariantBughouse ||
7204            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7205             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7206         board[fromY][fromX] = EmptySquare;
7207     } else if ((fromY == 3)
7208                && (toX != fromX)
7209                && gameInfo.variant != VariantXiangqi
7210                && gameInfo.variant != VariantBerolina
7211                && (board[fromY][fromX] == BlackPawn)
7212                && (board[toY][toX] == EmptySquare)) {
7213         board[fromY][fromX] = EmptySquare;
7214         board[toY][toX] = BlackPawn;
7215         captured = board[toY + 1][toX];
7216         board[toY + 1][toX] = EmptySquare;
7217     } else if ((fromY == 3)
7218                && (toX == fromX)
7219                && gameInfo.variant == VariantBerolina
7220                && (board[fromY][fromX] == BlackPawn)
7221                && (board[toY][toX] == EmptySquare)) {
7222         board[fromY][fromX] = EmptySquare;
7223         board[toY][toX] = BlackPawn;
7224         if(oldEP & EP_BEROLIN_A) {
7225                 captured = board[fromY][fromX-1];
7226                 board[fromY][fromX-1] = EmptySquare;
7227         }else{  captured = board[fromY][fromX+1];
7228                 board[fromY][fromX+1] = EmptySquare;
7229         }
7230     } else {
7231         board[toY][toX] = board[fromY][fromX];
7232         board[fromY][fromX] = EmptySquare;
7233     }
7234
7235     /* [HGM] now we promote for Shogi, if needed */
7236     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7237         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7238   }
7239
7240     if (gameInfo.holdingsWidth != 0) {
7241
7242       /* !!A lot more code needs to be written to support holdings  */
7243       /* [HGM] OK, so I have written it. Holdings are stored in the */
7244       /* penultimate board files, so they are automaticlly stored   */
7245       /* in the game history.                                       */
7246       if (fromY == DROP_RANK) {
7247         /* Delete from holdings, by decreasing count */
7248         /* and erasing image if necessary            */
7249         p = (int) fromX;
7250         if(p < (int) BlackPawn) { /* white drop */
7251              p -= (int)WhitePawn;
7252              if(p >= gameInfo.holdingsSize) p = 0;
7253              if(--board[p][BOARD_WIDTH-2] == 0)
7254                   board[p][BOARD_WIDTH-1] = EmptySquare;
7255         } else {                  /* black drop */
7256              p -= (int)BlackPawn;
7257              if(p >= gameInfo.holdingsSize) p = 0;
7258              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7259                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7260         }
7261       }
7262       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7263           && gameInfo.variant != VariantBughouse        ) {
7264         /* [HGM] holdings: Add to holdings, if holdings exist */
7265         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7266                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7267                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7268         }
7269         p = (int) captured;
7270         if (p >= (int) BlackPawn) {
7271           p -= (int)BlackPawn;
7272           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7273                   /* in Shogi restore piece to its original  first */
7274                   captured = (ChessSquare) (DEMOTED captured);
7275                   p = DEMOTED p;
7276           }
7277           p = PieceToNumber((ChessSquare)p);
7278           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7279           board[p][BOARD_WIDTH-2]++;
7280           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7281         } else {
7282           p -= (int)WhitePawn;
7283           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7284                   captured = (ChessSquare) (DEMOTED captured);
7285                   p = DEMOTED p;
7286           }
7287           p = PieceToNumber((ChessSquare)p);
7288           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7289           board[BOARD_HEIGHT-1-p][1]++;
7290           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7291         }
7292       }
7293
7294     } else if (gameInfo.variant == VariantAtomic) {
7295       if (captured != EmptySquare) {
7296         int y, x;
7297         for (y = toY-1; y <= toY+1; y++) {
7298           for (x = toX-1; x <= toX+1; x++) {
7299             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7300                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7301               board[y][x] = EmptySquare;
7302             }
7303           }
7304         }
7305         board[toY][toX] = EmptySquare;
7306       }
7307     }
7308     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7309         /* [HGM] Shogi promotions */
7310         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7311     }
7312
7313     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7314                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7315         // [HGM] superchess: take promotion piece out of holdings
7316         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7317         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7318             if(!--board[k][BOARD_WIDTH-2])
7319                 board[k][BOARD_WIDTH-1] = EmptySquare;
7320         } else {
7321             if(!--board[BOARD_HEIGHT-1-k][1])
7322                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7323         }
7324     }
7325
7326 }
7327
7328 /* Updates forwardMostMove */
7329 void
7330 MakeMove(fromX, fromY, toX, toY, promoChar)
7331      int fromX, fromY, toX, toY;
7332      int promoChar;
7333 {
7334 //    forwardMostMove++; // [HGM] bare: moved downstream
7335
7336     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7337         int timeLeft; static int lastLoadFlag=0; int king, piece;
7338         piece = boards[forwardMostMove][fromY][fromX];
7339         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7340         if(gameInfo.variant == VariantKnightmate)
7341             king += (int) WhiteUnicorn - (int) WhiteKing;
7342         if(forwardMostMove == 0) {
7343             if(blackPlaysFirst) 
7344                 fprintf(serverMoves, "%s;", second.tidy);
7345             fprintf(serverMoves, "%s;", first.tidy);
7346             if(!blackPlaysFirst) 
7347                 fprintf(serverMoves, "%s;", second.tidy);
7348         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7349         lastLoadFlag = loadFlag;
7350         // print base move
7351         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7352         // print castling suffix
7353         if( toY == fromY && piece == king ) {
7354             if(toX-fromX > 1)
7355                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7356             if(fromX-toX >1)
7357                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7358         }
7359         // e.p. suffix
7360         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7361              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7362              boards[forwardMostMove][toY][toX] == EmptySquare
7363              && fromX != toX )
7364                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7365         // promotion suffix
7366         if(promoChar != NULLCHAR)
7367                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7368         if(!loadFlag) {
7369             fprintf(serverMoves, "/%d/%d",
7370                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7371             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7372             else                      timeLeft = blackTimeRemaining/1000;
7373             fprintf(serverMoves, "/%d", timeLeft);
7374         }
7375         fflush(serverMoves);
7376     }
7377
7378     if (forwardMostMove+1 >= MAX_MOVES) {
7379       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7380                         0, 1);
7381       return;
7382     }
7383     if (commentList[forwardMostMove+1] != NULL) {
7384         free(commentList[forwardMostMove+1]);
7385         commentList[forwardMostMove+1] = NULL;
7386     }
7387     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7388     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7389     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7390                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7391     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7392     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7393     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7394     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7395     gameInfo.result = GameUnfinished;
7396     if (gameInfo.resultDetails != NULL) {
7397         free(gameInfo.resultDetails);
7398         gameInfo.resultDetails = NULL;
7399     }
7400     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7401                               moveList[forwardMostMove - 1]);
7402     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7403                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7404                              fromY, fromX, toY, toX, promoChar,
7405                              parseList[forwardMostMove - 1]);
7406     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7407                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7408                             castlingRights[forwardMostMove]) ) {
7409       case MT_NONE:
7410       case MT_STALEMATE:
7411       default:
7412         break;
7413       case MT_CHECK:
7414         if(gameInfo.variant != VariantShogi)
7415             strcat(parseList[forwardMostMove - 1], "+");
7416         break;
7417       case MT_CHECKMATE:
7418       case MT_STAINMATE:
7419         strcat(parseList[forwardMostMove - 1], "#");
7420         break;
7421     }
7422     if (appData.debugMode) {
7423         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7424     }
7425
7426 }
7427
7428 /* Updates currentMove if not pausing */
7429 void
7430 ShowMove(fromX, fromY, toX, toY)
7431 {
7432     int instant = (gameMode == PlayFromGameFile) ?
7433         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7434     if(appData.noGUI) return;
7435     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7436         if (!instant) {
7437             if (forwardMostMove == currentMove + 1) {
7438                 AnimateMove(boards[forwardMostMove - 1],
7439                             fromX, fromY, toX, toY);
7440             }
7441             if (appData.highlightLastMove) {
7442                 SetHighlights(fromX, fromY, toX, toY);
7443             }
7444         }
7445         currentMove = forwardMostMove;
7446     }
7447
7448     if (instant) return;
7449
7450     DisplayMove(currentMove - 1);
7451     DrawPosition(FALSE, boards[currentMove]);
7452     DisplayBothClocks();
7453     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7454 }
7455
7456 void SendEgtPath(ChessProgramState *cps)
7457 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7458         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7459
7460         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7461
7462         while(*p) {
7463             char c, *q = name+1, *r, *s;
7464
7465             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7466             while(*p && *p != ',') *q++ = *p++;
7467             *q++ = ':'; *q = 0;
7468             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7469                 strcmp(name, ",nalimov:") == 0 ) {
7470                 // take nalimov path from the menu-changeable option first, if it is defined
7471                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7472                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7473             } else
7474             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7475                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7476                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7477                 s = r = StrStr(s, ":") + 1; // beginning of path info
7478                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7479                 c = *r; *r = 0;             // temporarily null-terminate path info
7480                     *--q = 0;               // strip of trailig ':' from name
7481                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7482                 *r = c;
7483                 SendToProgram(buf,cps);     // send egtbpath command for this format
7484             }
7485             if(*p == ',') p++; // read away comma to position for next format name
7486         }
7487 }
7488
7489 void
7490 InitChessProgram(cps, setup)
7491      ChessProgramState *cps;
7492      int setup; /* [HGM] needed to setup FRC opening position */
7493 {
7494     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7495     if (appData.noChessProgram) return;
7496     hintRequested = FALSE;
7497     bookRequested = FALSE;
7498
7499     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7500     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7501     if(cps->memSize) { /* [HGM] memory */
7502         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7503         SendToProgram(buf, cps);
7504     }
7505     SendEgtPath(cps); /* [HGM] EGT */
7506     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7507         sprintf(buf, "cores %d\n", appData.smpCores);
7508         SendToProgram(buf, cps);
7509     }
7510
7511     SendToProgram(cps->initString, cps);
7512     if (gameInfo.variant != VariantNormal &&
7513         gameInfo.variant != VariantLoadable
7514         /* [HGM] also send variant if board size non-standard */
7515         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7516                                             ) {
7517       char *v = VariantName(gameInfo.variant);
7518       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7519         /* [HGM] in protocol 1 we have to assume all variants valid */
7520         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7521         DisplayFatalError(buf, 0, 1);
7522         return;
7523       }
7524
7525       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7526       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7527       if( gameInfo.variant == VariantXiangqi )
7528            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7529       if( gameInfo.variant == VariantShogi )
7530            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7531       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7532            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7533       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7534                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7535            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7536       if( gameInfo.variant == VariantCourier )
7537            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7538       if( gameInfo.variant == VariantSuper )
7539            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7540       if( gameInfo.variant == VariantGreat )
7541            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7542
7543       if(overruled) {
7544            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7545                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7546            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7547            if(StrStr(cps->variants, b) == NULL) { 
7548                // specific sized variant not known, check if general sizing allowed
7549                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7550                    if(StrStr(cps->variants, "boardsize") == NULL) {
7551                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7552                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7553                        DisplayFatalError(buf, 0, 1);
7554                        return;
7555                    }
7556                    /* [HGM] here we really should compare with the maximum supported board size */
7557                }
7558            }
7559       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7560       sprintf(buf, "variant %s\n", b);
7561       SendToProgram(buf, cps);
7562     }
7563     currentlyInitializedVariant = gameInfo.variant;
7564
7565     /* [HGM] send opening position in FRC to first engine */
7566     if(setup) {
7567           SendToProgram("force\n", cps);
7568           SendBoard(cps, 0);
7569           /* engine is now in force mode! Set flag to wake it up after first move. */
7570           setboardSpoiledMachineBlack = 1;
7571     }
7572
7573     if (cps->sendICS) {
7574       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7575       SendToProgram(buf, cps);
7576     }
7577     cps->maybeThinking = FALSE;
7578     cps->offeredDraw = 0;
7579     if (!appData.icsActive) {
7580         SendTimeControl(cps, movesPerSession, timeControl,
7581                         timeIncrement, appData.searchDepth,
7582                         searchTime);
7583     }
7584     if (appData.showThinking 
7585         // [HGM] thinking: four options require thinking output to be sent
7586         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7587                                 ) {
7588         SendToProgram("post\n", cps);
7589     }
7590     SendToProgram("hard\n", cps);
7591     if (!appData.ponderNextMove) {
7592         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7593            it without being sure what state we are in first.  "hard"
7594            is not a toggle, so that one is OK.
7595          */
7596         SendToProgram("easy\n", cps);
7597     }
7598     if (cps->usePing) {
7599       sprintf(buf, "ping %d\n", ++cps->lastPing);
7600       SendToProgram(buf, cps);
7601     }
7602     cps->initDone = TRUE;
7603 }   
7604
7605
7606 void
7607 StartChessProgram(cps)
7608      ChessProgramState *cps;
7609 {
7610     char buf[MSG_SIZ];
7611     int err;
7612
7613     if (appData.noChessProgram) return;
7614     cps->initDone = FALSE;
7615
7616     if (strcmp(cps->host, "localhost") == 0) {
7617         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7618     } else if (*appData.remoteShell == NULLCHAR) {
7619         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7620     } else {
7621         if (*appData.remoteUser == NULLCHAR) {
7622           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7623                     cps->program);
7624         } else {
7625           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7626                     cps->host, appData.remoteUser, cps->program);
7627         }
7628         err = StartChildProcess(buf, "", &cps->pr);
7629     }
7630     
7631     if (err != 0) {
7632         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7633         DisplayFatalError(buf, err, 1);
7634         cps->pr = NoProc;
7635         cps->isr = NULL;
7636         return;
7637     }
7638     
7639     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7640     if (cps->protocolVersion > 1) {
7641       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7642       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7643       cps->comboCnt = 0;  //                and values of combo boxes
7644       SendToProgram(buf, cps);
7645     } else {
7646       SendToProgram("xboard\n", cps);
7647     }
7648 }
7649
7650
7651 void
7652 TwoMachinesEventIfReady P((void))
7653 {
7654   if (first.lastPing != first.lastPong) {
7655     DisplayMessage("", _("Waiting for first chess program"));
7656     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7657     return;
7658   }
7659   if (second.lastPing != second.lastPong) {
7660     DisplayMessage("", _("Waiting for second chess program"));
7661     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7662     return;
7663   }
7664   ThawUI();
7665   TwoMachinesEvent();
7666 }
7667
7668 void
7669 NextMatchGame P((void))
7670 {
7671     int index; /* [HGM] autoinc: step lod index during match */
7672     Reset(FALSE, TRUE);
7673     if (*appData.loadGameFile != NULLCHAR) {
7674         index = appData.loadGameIndex;
7675         if(index < 0) { // [HGM] autoinc
7676             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7677             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7678         } 
7679         LoadGameFromFile(appData.loadGameFile,
7680                          index,
7681                          appData.loadGameFile, FALSE);
7682     } else if (*appData.loadPositionFile != NULLCHAR) {
7683         index = appData.loadPositionIndex;
7684         if(index < 0) { // [HGM] autoinc
7685             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7686             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7687         } 
7688         LoadPositionFromFile(appData.loadPositionFile,
7689                              index,
7690                              appData.loadPositionFile);
7691     }
7692     TwoMachinesEventIfReady();
7693 }
7694
7695 void UserAdjudicationEvent( int result )
7696 {
7697     ChessMove gameResult = GameIsDrawn;
7698
7699     if( result > 0 ) {
7700         gameResult = WhiteWins;
7701     }
7702     else if( result < 0 ) {
7703         gameResult = BlackWins;
7704     }
7705
7706     if( gameMode == TwoMachinesPlay ) {
7707         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7708     }
7709 }
7710
7711
7712 // [HGM] save: calculate checksum of game to make games easily identifiable
7713 int StringCheckSum(char *s)
7714 {
7715         int i = 0;
7716         if(s==NULL) return 0;
7717         while(*s) i = i*259 + *s++;
7718         return i;
7719 }
7720
7721 int GameCheckSum()
7722 {
7723         int i, sum=0;
7724         for(i=backwardMostMove; i<forwardMostMove; i++) {
7725                 sum += pvInfoList[i].depth;
7726                 sum += StringCheckSum(parseList[i]);
7727                 sum += StringCheckSum(commentList[i]);
7728                 sum *= 261;
7729         }
7730         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7731         return sum + StringCheckSum(commentList[i]);
7732 } // end of save patch
7733
7734 void
7735 GameEnds(result, resultDetails, whosays)
7736      ChessMove result;
7737      char *resultDetails;
7738      int whosays;
7739 {
7740     GameMode nextGameMode;
7741     int isIcsGame;
7742     char buf[MSG_SIZ];
7743
7744     if(endingGame) return; /* [HGM] crash: forbid recursion */
7745     endingGame = 1;
7746
7747     if (appData.debugMode) {
7748       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7749               result, resultDetails ? resultDetails : "(null)", whosays);
7750     }
7751
7752     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7753         /* If we are playing on ICS, the server decides when the
7754            game is over, but the engine can offer to draw, claim 
7755            a draw, or resign. 
7756          */
7757 #if ZIPPY
7758         if (appData.zippyPlay && first.initDone) {
7759             if (result == GameIsDrawn) {
7760                 /* In case draw still needs to be claimed */
7761                 SendToICS(ics_prefix);
7762                 SendToICS("draw\n");
7763             } else if (StrCaseStr(resultDetails, "resign")) {
7764                 SendToICS(ics_prefix);
7765                 SendToICS("resign\n");
7766             }
7767         }
7768 #endif
7769         endingGame = 0; /* [HGM] crash */
7770         return;
7771     }
7772
7773     /* If we're loading the game from a file, stop */
7774     if (whosays == GE_FILE) {
7775       (void) StopLoadGameTimer();
7776       gameFileFP = NULL;
7777     }
7778
7779     /* Cancel draw offers */
7780     first.offeredDraw = second.offeredDraw = 0;
7781
7782     /* If this is an ICS game, only ICS can really say it's done;
7783        if not, anyone can. */
7784     isIcsGame = (gameMode == IcsPlayingWhite || 
7785                  gameMode == IcsPlayingBlack || 
7786                  gameMode == IcsObserving    || 
7787                  gameMode == IcsExamining);
7788
7789     if (!isIcsGame || whosays == GE_ICS) {
7790         /* OK -- not an ICS game, or ICS said it was done */
7791         StopClocks();
7792         if (!isIcsGame && !appData.noChessProgram) 
7793           SetUserThinkingEnables();
7794     
7795         /* [HGM] if a machine claims the game end we verify this claim */
7796         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7797             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7798                 char claimer;
7799                 ChessMove trueResult = (ChessMove) -1;
7800
7801                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7802                                             first.twoMachinesColor[0] :
7803                                             second.twoMachinesColor[0] ;
7804
7805                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7806                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7807                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7808                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7809                 } else
7810                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7811                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7812                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7813                 } else
7814                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7815                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7816                 }
7817
7818                 // now verify win claims, but not in drop games, as we don't understand those yet
7819                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7820                                                  || gameInfo.variant == VariantGreat) &&
7821                     (result == WhiteWins && claimer == 'w' ||
7822                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7823                       if (appData.debugMode) {
7824                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7825                                 result, epStatus[forwardMostMove], forwardMostMove);
7826                       }
7827                       if(result != trueResult) {
7828                               sprintf(buf, "False win claim: '%s'", resultDetails);
7829                               result = claimer == 'w' ? BlackWins : WhiteWins;
7830                               resultDetails = buf;
7831                       }
7832                 } else
7833                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7834                     && (forwardMostMove <= backwardMostMove ||
7835                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7836                         (claimer=='b')==(forwardMostMove&1))
7837                                                                                   ) {
7838                       /* [HGM] verify: draws that were not flagged are false claims */
7839                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7840                       result = claimer == 'w' ? BlackWins : WhiteWins;
7841                       resultDetails = buf;
7842                 }
7843                 /* (Claiming a loss is accepted no questions asked!) */
7844             }
7845             /* [HGM] bare: don't allow bare King to win */
7846             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7847                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7848                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7849                && result != GameIsDrawn)
7850             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7851                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7852                         int p = (int)boards[forwardMostMove][i][j] - color;
7853                         if(p >= 0 && p <= (int)WhiteKing) k++;
7854                 }
7855                 if (appData.debugMode) {
7856                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7857                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7858                 }
7859                 if(k <= 1) {
7860                         result = GameIsDrawn;
7861                         sprintf(buf, "%s but bare king", resultDetails);
7862                         resultDetails = buf;
7863                 }
7864             }
7865         }
7866
7867
7868         if(serverMoves != NULL && !loadFlag) { char c = '=';
7869             if(result==WhiteWins) c = '+';
7870             if(result==BlackWins) c = '-';
7871             if(resultDetails != NULL)
7872                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7873         }
7874         if (resultDetails != NULL) {
7875             gameInfo.result = result;
7876             gameInfo.resultDetails = StrSave(resultDetails);
7877
7878             /* display last move only if game was not loaded from file */
7879             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7880                 DisplayMove(currentMove - 1);
7881     
7882             if (forwardMostMove != 0) {
7883                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7884                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7885                                                                 ) {
7886                     if (*appData.saveGameFile != NULLCHAR) {
7887                         SaveGameToFile(appData.saveGameFile, TRUE);
7888                     } else if (appData.autoSaveGames) {
7889                         AutoSaveGame();
7890                     }
7891                     if (*appData.savePositionFile != NULLCHAR) {
7892                         SavePositionToFile(appData.savePositionFile);
7893                     }
7894                 }
7895             }
7896
7897             /* Tell program how game ended in case it is learning */
7898             /* [HGM] Moved this to after saving the PGN, just in case */
7899             /* engine died and we got here through time loss. In that */
7900             /* case we will get a fatal error writing the pipe, which */
7901             /* would otherwise lose us the PGN.                       */
7902             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7903             /* output during GameEnds should never be fatal anymore   */
7904             if (gameMode == MachinePlaysWhite ||
7905                 gameMode == MachinePlaysBlack ||
7906                 gameMode == TwoMachinesPlay ||
7907                 gameMode == IcsPlayingWhite ||
7908                 gameMode == IcsPlayingBlack ||
7909                 gameMode == BeginningOfGame) {
7910                 char buf[MSG_SIZ];
7911                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7912                         resultDetails);
7913                 if (first.pr != NoProc) {
7914                     SendToProgram(buf, &first);
7915                 }
7916                 if (second.pr != NoProc &&
7917                     gameMode == TwoMachinesPlay) {
7918                     SendToProgram(buf, &second);
7919                 }
7920             }
7921         }
7922
7923         if (appData.icsActive) {
7924             if (appData.quietPlay &&
7925                 (gameMode == IcsPlayingWhite ||
7926                  gameMode == IcsPlayingBlack)) {
7927                 SendToICS(ics_prefix);
7928                 SendToICS("set shout 1\n");
7929             }
7930             nextGameMode = IcsIdle;
7931             ics_user_moved = FALSE;
7932             /* clean up premove.  It's ugly when the game has ended and the
7933              * premove highlights are still on the board.
7934              */
7935             if (gotPremove) {
7936               gotPremove = FALSE;
7937               ClearPremoveHighlights();
7938               DrawPosition(FALSE, boards[currentMove]);
7939             }
7940             if (whosays == GE_ICS) {
7941                 switch (result) {
7942                 case WhiteWins:
7943                     if (gameMode == IcsPlayingWhite)
7944                         PlayIcsWinSound();
7945                     else if(gameMode == IcsPlayingBlack)
7946                         PlayIcsLossSound();
7947                     break;
7948                 case BlackWins:
7949                     if (gameMode == IcsPlayingBlack)
7950                         PlayIcsWinSound();
7951                     else if(gameMode == IcsPlayingWhite)
7952                         PlayIcsLossSound();
7953                     break;
7954                 case GameIsDrawn:
7955                     PlayIcsDrawSound();
7956                     break;
7957                 default:
7958                     PlayIcsUnfinishedSound();
7959                 }
7960             }
7961         } else if (gameMode == EditGame ||
7962                    gameMode == PlayFromGameFile || 
7963                    gameMode == AnalyzeMode || 
7964                    gameMode == AnalyzeFile) {
7965             nextGameMode = gameMode;
7966         } else {
7967             nextGameMode = EndOfGame;
7968         }
7969         pausing = FALSE;
7970         ModeHighlight();
7971     } else {
7972         nextGameMode = gameMode;
7973     }
7974
7975     if (appData.noChessProgram) {
7976         gameMode = nextGameMode;
7977         ModeHighlight();
7978         endingGame = 0; /* [HGM] crash */
7979         return;
7980     }
7981
7982     if (first.reuse) {
7983         /* Put first chess program into idle state */
7984         if (first.pr != NoProc &&
7985             (gameMode == MachinePlaysWhite ||
7986              gameMode == MachinePlaysBlack ||
7987              gameMode == TwoMachinesPlay ||
7988              gameMode == IcsPlayingWhite ||
7989              gameMode == IcsPlayingBlack ||
7990              gameMode == BeginningOfGame)) {
7991             SendToProgram("force\n", &first);
7992             if (first.usePing) {
7993               char buf[MSG_SIZ];
7994               sprintf(buf, "ping %d\n", ++first.lastPing);
7995               SendToProgram(buf, &first);
7996             }
7997         }
7998     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7999         /* Kill off first chess program */
8000         if (first.isr != NULL)
8001           RemoveInputSource(first.isr);
8002         first.isr = NULL;
8003     
8004         if (first.pr != NoProc) {
8005             ExitAnalyzeMode();
8006             DoSleep( appData.delayBeforeQuit );
8007             SendToProgram("quit\n", &first);
8008             DoSleep( appData.delayAfterQuit );
8009             DestroyChildProcess(first.pr, first.useSigterm);
8010         }
8011         first.pr = NoProc;
8012     }
8013     if (second.reuse) {
8014         /* Put second chess program into idle state */
8015         if (second.pr != NoProc &&
8016             gameMode == TwoMachinesPlay) {
8017             SendToProgram("force\n", &second);
8018             if (second.usePing) {
8019               char buf[MSG_SIZ];
8020               sprintf(buf, "ping %d\n", ++second.lastPing);
8021               SendToProgram(buf, &second);
8022             }
8023         }
8024     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8025         /* Kill off second chess program */
8026         if (second.isr != NULL)
8027           RemoveInputSource(second.isr);
8028         second.isr = NULL;
8029     
8030         if (second.pr != NoProc) {
8031             DoSleep( appData.delayBeforeQuit );
8032             SendToProgram("quit\n", &second);
8033             DoSleep( appData.delayAfterQuit );
8034             DestroyChildProcess(second.pr, second.useSigterm);
8035         }
8036         second.pr = NoProc;
8037     }
8038
8039     if (matchMode && gameMode == TwoMachinesPlay) {
8040         switch (result) {
8041         case WhiteWins:
8042           if (first.twoMachinesColor[0] == 'w') {
8043             first.matchWins++;
8044           } else {
8045             second.matchWins++;
8046           }
8047           break;
8048         case BlackWins:
8049           if (first.twoMachinesColor[0] == 'b') {
8050             first.matchWins++;
8051           } else {
8052             second.matchWins++;
8053           }
8054           break;
8055         default:
8056           break;
8057         }
8058         if (matchGame < appData.matchGames) {
8059             char *tmp;
8060             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8061                 tmp = first.twoMachinesColor;
8062                 first.twoMachinesColor = second.twoMachinesColor;
8063                 second.twoMachinesColor = tmp;
8064             }
8065             gameMode = nextGameMode;
8066             matchGame++;
8067             if(appData.matchPause>10000 || appData.matchPause<10)
8068                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8069             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8070             endingGame = 0; /* [HGM] crash */
8071             return;
8072         } else {
8073             char buf[MSG_SIZ];
8074             gameMode = nextGameMode;
8075             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8076                     first.tidy, second.tidy,
8077                     first.matchWins, second.matchWins,
8078                     appData.matchGames - (first.matchWins + second.matchWins));
8079             DisplayFatalError(buf, 0, 0);
8080         }
8081     }
8082     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8083         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8084       ExitAnalyzeMode();
8085     gameMode = nextGameMode;
8086     ModeHighlight();
8087     endingGame = 0;  /* [HGM] crash */
8088 }
8089
8090 /* Assumes program was just initialized (initString sent).
8091    Leaves program in force mode. */
8092 void
8093 FeedMovesToProgram(cps, upto) 
8094      ChessProgramState *cps;
8095      int upto;
8096 {
8097     int i;
8098     
8099     if (appData.debugMode)
8100       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8101               startedFromSetupPosition ? "position and " : "",
8102               backwardMostMove, upto, cps->which);
8103     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8104         // [HGM] variantswitch: make engine aware of new variant
8105         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8106                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8107         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8108         SendToProgram(buf, cps);
8109         currentlyInitializedVariant = gameInfo.variant;
8110     }
8111     SendToProgram("force\n", cps);
8112     if (startedFromSetupPosition) {
8113         SendBoard(cps, backwardMostMove);
8114     if (appData.debugMode) {
8115         fprintf(debugFP, "feedMoves\n");
8116     }
8117     }
8118     for (i = backwardMostMove; i < upto; i++) {
8119         SendMoveToProgram(i, cps);
8120     }
8121 }
8122
8123
8124 void
8125 ResurrectChessProgram()
8126 {
8127      /* The chess program may have exited.
8128         If so, restart it and feed it all the moves made so far. */
8129
8130     if (appData.noChessProgram || first.pr != NoProc) return;
8131     
8132     StartChessProgram(&first);
8133     InitChessProgram(&first, FALSE);
8134     FeedMovesToProgram(&first, currentMove);
8135
8136     if (!first.sendTime) {
8137         /* can't tell gnuchess what its clock should read,
8138            so we bow to its notion. */
8139         ResetClocks();
8140         timeRemaining[0][currentMove] = whiteTimeRemaining;
8141         timeRemaining[1][currentMove] = blackTimeRemaining;
8142     }
8143
8144     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8145                 appData.icsEngineAnalyze) && first.analysisSupport) {
8146       SendToProgram("analyze\n", &first);
8147       first.analyzing = TRUE;
8148     }
8149 }
8150
8151 /*
8152  * Button procedures
8153  */
8154 void
8155 Reset(redraw, init)
8156      int redraw, init;
8157 {
8158     int i;
8159
8160     if (appData.debugMode) {
8161         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8162                 redraw, init, gameMode);
8163     }
8164     pausing = pauseExamInvalid = FALSE;
8165     startedFromSetupPosition = blackPlaysFirst = FALSE;
8166     firstMove = TRUE;
8167     whiteFlag = blackFlag = FALSE;
8168     userOfferedDraw = FALSE;
8169     hintRequested = bookRequested = FALSE;
8170     first.maybeThinking = FALSE;
8171     second.maybeThinking = FALSE;
8172     first.bookSuspend = FALSE; // [HGM] book
8173     second.bookSuspend = FALSE;
8174     thinkOutput[0] = NULLCHAR;
8175     lastHint[0] = NULLCHAR;
8176     ClearGameInfo(&gameInfo);
8177     gameInfo.variant = StringToVariant(appData.variant);
8178     ics_user_moved = ics_clock_paused = FALSE;
8179     ics_getting_history = H_FALSE;
8180     ics_gamenum = -1;
8181     white_holding[0] = black_holding[0] = NULLCHAR;
8182     ClearProgramStats();
8183     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8184     
8185     ResetFrontEnd();
8186     ClearHighlights();
8187     flipView = appData.flipView;
8188     ClearPremoveHighlights();
8189     gotPremove = FALSE;
8190     alarmSounded = FALSE;
8191
8192     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8193     if(appData.serverMovesName != NULL) {
8194         /* [HGM] prepare to make moves file for broadcasting */
8195         clock_t t = clock();
8196         if(serverMoves != NULL) fclose(serverMoves);
8197         serverMoves = fopen(appData.serverMovesName, "r");
8198         if(serverMoves != NULL) {
8199             fclose(serverMoves);
8200             /* delay 15 sec before overwriting, so all clients can see end */
8201             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8202         }
8203         serverMoves = fopen(appData.serverMovesName, "w");
8204     }
8205
8206     ExitAnalyzeMode();
8207     gameMode = BeginningOfGame;
8208     ModeHighlight();
8209     if(appData.icsActive) gameInfo.variant = VariantNormal;
8210     currentMove = forwardMostMove = backwardMostMove = 0;
8211     InitPosition(redraw);
8212     for (i = 0; i < MAX_MOVES; i++) {
8213         if (commentList[i] != NULL) {
8214             free(commentList[i]);
8215             commentList[i] = NULL;
8216         }
8217     }
8218     ResetClocks();
8219     timeRemaining[0][0] = whiteTimeRemaining;
8220     timeRemaining[1][0] = blackTimeRemaining;
8221     if (first.pr == NULL) {
8222         StartChessProgram(&first);
8223     }
8224     if (init) {
8225             InitChessProgram(&first, startedFromSetupPosition);
8226     }
8227     DisplayTitle("");
8228     DisplayMessage("", "");
8229     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8230     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8231 }
8232
8233 void
8234 AutoPlayGameLoop()
8235 {
8236     for (;;) {
8237         if (!AutoPlayOneMove())
8238           return;
8239         if (matchMode || appData.timeDelay == 0)
8240           continue;
8241         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8242           return;
8243         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8244         break;
8245     }
8246 }
8247
8248
8249 int
8250 AutoPlayOneMove()
8251 {
8252     int fromX, fromY, toX, toY;
8253
8254     if (appData.debugMode) {
8255       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8256     }
8257
8258     if (gameMode != PlayFromGameFile)
8259       return FALSE;
8260
8261     if (currentMove >= forwardMostMove) {
8262       gameMode = EditGame;
8263       ModeHighlight();
8264
8265       /* [AS] Clear current move marker at the end of a game */
8266       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8267
8268       return FALSE;
8269     }
8270     
8271     toX = moveList[currentMove][2] - AAA;
8272     toY = moveList[currentMove][3] - ONE;
8273
8274     if (moveList[currentMove][1] == '@') {
8275         if (appData.highlightLastMove) {
8276             SetHighlights(-1, -1, toX, toY);
8277         }
8278     } else {
8279         fromX = moveList[currentMove][0] - AAA;
8280         fromY = moveList[currentMove][1] - ONE;
8281
8282         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8283
8284         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8285
8286         if (appData.highlightLastMove) {
8287             SetHighlights(fromX, fromY, toX, toY);
8288         }
8289     }
8290     DisplayMove(currentMove);
8291     SendMoveToProgram(currentMove++, &first);
8292     DisplayBothClocks();
8293     DrawPosition(FALSE, boards[currentMove]);
8294     // [HGM] PV info: always display, routine tests if empty
8295     DisplayComment(currentMove - 1, commentList[currentMove]);
8296     return TRUE;
8297 }
8298
8299
8300 int
8301 LoadGameOneMove(readAhead)
8302      ChessMove readAhead;
8303 {
8304     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8305     char promoChar = NULLCHAR;
8306     ChessMove moveType;
8307     char move[MSG_SIZ];
8308     char *p, *q;
8309     
8310     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8311         gameMode != AnalyzeMode && gameMode != Training) {
8312         gameFileFP = NULL;
8313         return FALSE;
8314     }
8315     
8316     yyboardindex = forwardMostMove;
8317     if (readAhead != (ChessMove)0) {
8318       moveType = readAhead;
8319     } else {
8320       if (gameFileFP == NULL)
8321           return FALSE;
8322       moveType = (ChessMove) yylex();
8323     }
8324     
8325     done = FALSE;
8326     switch (moveType) {
8327       case Comment:
8328         if (appData.debugMode) 
8329           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8330         p = yy_text;
8331         if (*p == '{' || *p == '[' || *p == '(') {
8332             p[strlen(p) - 1] = NULLCHAR;
8333             p++;
8334         }
8335
8336         /* append the comment but don't display it */
8337         while (*p == '\n') p++;
8338         AppendComment(currentMove, p);
8339         return TRUE;
8340
8341       case WhiteCapturesEnPassant:
8342       case BlackCapturesEnPassant:
8343       case WhitePromotionChancellor:
8344       case BlackPromotionChancellor:
8345       case WhitePromotionArchbishop:
8346       case BlackPromotionArchbishop:
8347       case WhitePromotionCentaur:
8348       case BlackPromotionCentaur:
8349       case WhitePromotionQueen:
8350       case BlackPromotionQueen:
8351       case WhitePromotionRook:
8352       case BlackPromotionRook:
8353       case WhitePromotionBishop:
8354       case BlackPromotionBishop:
8355       case WhitePromotionKnight:
8356       case BlackPromotionKnight:
8357       case WhitePromotionKing:
8358       case BlackPromotionKing:
8359       case NormalMove:
8360       case WhiteKingSideCastle:
8361       case WhiteQueenSideCastle:
8362       case BlackKingSideCastle:
8363       case BlackQueenSideCastle:
8364       case WhiteKingSideCastleWild:
8365       case WhiteQueenSideCastleWild:
8366       case BlackKingSideCastleWild:
8367       case BlackQueenSideCastleWild:
8368       /* PUSH Fabien */
8369       case WhiteHSideCastleFR:
8370       case WhiteASideCastleFR:
8371       case BlackHSideCastleFR:
8372       case BlackASideCastleFR:
8373       /* POP Fabien */
8374         if (appData.debugMode)
8375           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8376         fromX = currentMoveString[0] - AAA;
8377         fromY = currentMoveString[1] - ONE;
8378         toX = currentMoveString[2] - AAA;
8379         toY = currentMoveString[3] - ONE;
8380         promoChar = currentMoveString[4];
8381         break;
8382
8383       case WhiteDrop:
8384       case BlackDrop:
8385         if (appData.debugMode)
8386           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8387         fromX = moveType == WhiteDrop ?
8388           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8389         (int) CharToPiece(ToLower(currentMoveString[0]));
8390         fromY = DROP_RANK;
8391         toX = currentMoveString[2] - AAA;
8392         toY = currentMoveString[3] - ONE;
8393         break;
8394
8395       case WhiteWins:
8396       case BlackWins:
8397       case GameIsDrawn:
8398       case GameUnfinished:
8399         if (appData.debugMode)
8400           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8401         p = strchr(yy_text, '{');
8402         if (p == NULL) p = strchr(yy_text, '(');
8403         if (p == NULL) {
8404             p = yy_text;
8405             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8406         } else {
8407             q = strchr(p, *p == '{' ? '}' : ')');
8408             if (q != NULL) *q = NULLCHAR;
8409             p++;
8410         }
8411         GameEnds(moveType, p, GE_FILE);
8412         done = TRUE;
8413         if (cmailMsgLoaded) {
8414             ClearHighlights();
8415             flipView = WhiteOnMove(currentMove);
8416             if (moveType == GameUnfinished) flipView = !flipView;
8417             if (appData.debugMode)
8418               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8419         }
8420         break;
8421
8422       case (ChessMove) 0:       /* end of file */
8423         if (appData.debugMode)
8424           fprintf(debugFP, "Parser hit end of file\n");
8425         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8426                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8427           case MT_NONE:
8428           case MT_CHECK:
8429             break;
8430           case MT_CHECKMATE:
8431           case MT_STAINMATE:
8432             if (WhiteOnMove(currentMove)) {
8433                 GameEnds(BlackWins, "Black mates", GE_FILE);
8434             } else {
8435                 GameEnds(WhiteWins, "White mates", GE_FILE);
8436             }
8437             break;
8438           case MT_STALEMATE:
8439             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8440             break;
8441         }
8442         done = TRUE;
8443         break;
8444
8445       case MoveNumberOne:
8446         if (lastLoadGameStart == GNUChessGame) {
8447             /* GNUChessGames have numbers, but they aren't move numbers */
8448             if (appData.debugMode)
8449               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8450                       yy_text, (int) moveType);
8451             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8452         }
8453         /* else fall thru */
8454
8455       case XBoardGame:
8456       case GNUChessGame:
8457       case PGNTag:
8458         /* Reached start of next game in file */
8459         if (appData.debugMode)
8460           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8461         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8462                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8463           case MT_NONE:
8464           case MT_CHECK:
8465             break;
8466           case MT_CHECKMATE:
8467           case MT_STAINMATE:
8468             if (WhiteOnMove(currentMove)) {
8469                 GameEnds(BlackWins, "Black mates", GE_FILE);
8470             } else {
8471                 GameEnds(WhiteWins, "White mates", GE_FILE);
8472             }
8473             break;
8474           case MT_STALEMATE:
8475             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8476             break;
8477         }
8478         done = TRUE;
8479         break;
8480
8481       case PositionDiagram:     /* should not happen; ignore */
8482       case ElapsedTime:         /* ignore */
8483       case NAG:                 /* ignore */
8484         if (appData.debugMode)
8485           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8486                   yy_text, (int) moveType);
8487         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8488
8489       case IllegalMove:
8490         if (appData.testLegality) {
8491             if (appData.debugMode)
8492               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8493             sprintf(move, _("Illegal move: %d.%s%s"),
8494                     (forwardMostMove / 2) + 1,
8495                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8496             DisplayError(move, 0);
8497             done = TRUE;
8498         } else {
8499             if (appData.debugMode)
8500               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8501                       yy_text, currentMoveString);
8502             fromX = currentMoveString[0] - AAA;
8503             fromY = currentMoveString[1] - ONE;
8504             toX = currentMoveString[2] - AAA;
8505             toY = currentMoveString[3] - ONE;
8506             promoChar = currentMoveString[4];
8507         }
8508         break;
8509
8510       case AmbiguousMove:
8511         if (appData.debugMode)
8512           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8513         sprintf(move, _("Ambiguous move: %d.%s%s"),
8514                 (forwardMostMove / 2) + 1,
8515                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8516         DisplayError(move, 0);
8517         done = TRUE;
8518         break;
8519
8520       default:
8521       case ImpossibleMove:
8522         if (appData.debugMode)
8523           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8524         sprintf(move, _("Illegal move: %d.%s%s"),
8525                 (forwardMostMove / 2) + 1,
8526                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8527         DisplayError(move, 0);
8528         done = TRUE;
8529         break;
8530     }
8531
8532     if (done) {
8533         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8534             DrawPosition(FALSE, boards[currentMove]);
8535             DisplayBothClocks();
8536             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8537               DisplayComment(currentMove - 1, commentList[currentMove]);
8538         }
8539         (void) StopLoadGameTimer();
8540         gameFileFP = NULL;
8541         cmailOldMove = forwardMostMove;
8542         return FALSE;
8543     } else {
8544         /* currentMoveString is set as a side-effect of yylex */
8545         strcat(currentMoveString, "\n");
8546         strcpy(moveList[forwardMostMove], currentMoveString);
8547         
8548         thinkOutput[0] = NULLCHAR;
8549         MakeMove(fromX, fromY, toX, toY, promoChar);
8550         currentMove = forwardMostMove;
8551         return TRUE;
8552     }
8553 }
8554
8555 /* Load the nth game from the given file */
8556 int
8557 LoadGameFromFile(filename, n, title, useList)
8558      char *filename;
8559      int n;
8560      char *title;
8561      /*Boolean*/ int useList;
8562 {
8563     FILE *f;
8564     char buf[MSG_SIZ];
8565
8566     if (strcmp(filename, "-") == 0) {
8567         f = stdin;
8568         title = "stdin";
8569     } else {
8570         f = fopen(filename, "rb");
8571         if (f == NULL) {
8572           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8573             DisplayError(buf, errno);
8574             return FALSE;
8575         }
8576     }
8577     if (fseek(f, 0, 0) == -1) {
8578         /* f is not seekable; probably a pipe */
8579         useList = FALSE;
8580     }
8581     if (useList && n == 0) {
8582         int error = GameListBuild(f);
8583         if (error) {
8584             DisplayError(_("Cannot build game list"), error);
8585         } else if (!ListEmpty(&gameList) &&
8586                    ((ListGame *) gameList.tailPred)->number > 1) {
8587             GameListPopUp(f, title);
8588             return TRUE;
8589         }
8590         GameListDestroy();
8591         n = 1;
8592     }
8593     if (n == 0) n = 1;
8594     return LoadGame(f, n, title, FALSE);
8595 }
8596
8597
8598 void
8599 MakeRegisteredMove()
8600 {
8601     int fromX, fromY, toX, toY;
8602     char promoChar;
8603     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8604         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8605           case CMAIL_MOVE:
8606           case CMAIL_DRAW:
8607             if (appData.debugMode)
8608               fprintf(debugFP, "Restoring %s for game %d\n",
8609                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8610     
8611             thinkOutput[0] = NULLCHAR;
8612             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8613             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8614             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8615             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8616             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8617             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8618             MakeMove(fromX, fromY, toX, toY, promoChar);
8619             ShowMove(fromX, fromY, toX, toY);
8620               
8621             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8622                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8623               case MT_NONE:
8624               case MT_CHECK:
8625                 break;
8626                 
8627               case MT_CHECKMATE:
8628               case MT_STAINMATE:
8629                 if (WhiteOnMove(currentMove)) {
8630                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8631                 } else {
8632                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8633                 }
8634                 break;
8635                 
8636               case MT_STALEMATE:
8637                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8638                 break;
8639             }
8640
8641             break;
8642             
8643           case CMAIL_RESIGN:
8644             if (WhiteOnMove(currentMove)) {
8645                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8646             } else {
8647                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8648             }
8649             break;
8650             
8651           case CMAIL_ACCEPT:
8652             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8653             break;
8654               
8655           default:
8656             break;
8657         }
8658     }
8659
8660     return;
8661 }
8662
8663 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8664 int
8665 CmailLoadGame(f, gameNumber, title, useList)
8666      FILE *f;
8667      int gameNumber;
8668      char *title;
8669      int useList;
8670 {
8671     int retVal;
8672
8673     if (gameNumber > nCmailGames) {
8674         DisplayError(_("No more games in this message"), 0);
8675         return FALSE;
8676     }
8677     if (f == lastLoadGameFP) {
8678         int offset = gameNumber - lastLoadGameNumber;
8679         if (offset == 0) {
8680             cmailMsg[0] = NULLCHAR;
8681             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8682                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8683                 nCmailMovesRegistered--;
8684             }
8685             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8686             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8687                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8688             }
8689         } else {
8690             if (! RegisterMove()) return FALSE;
8691         }
8692     }
8693
8694     retVal = LoadGame(f, gameNumber, title, useList);
8695
8696     /* Make move registered during previous look at this game, if any */
8697     MakeRegisteredMove();
8698
8699     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8700         commentList[currentMove]
8701           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8702         DisplayComment(currentMove - 1, commentList[currentMove]);
8703     }
8704
8705     return retVal;
8706 }
8707
8708 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8709 int
8710 ReloadGame(offset)
8711      int offset;
8712 {
8713     int gameNumber = lastLoadGameNumber + offset;
8714     if (lastLoadGameFP == NULL) {
8715         DisplayError(_("No game has been loaded yet"), 0);
8716         return FALSE;
8717     }
8718     if (gameNumber <= 0) {
8719         DisplayError(_("Can't back up any further"), 0);
8720         return FALSE;
8721     }
8722     if (cmailMsgLoaded) {
8723         return CmailLoadGame(lastLoadGameFP, gameNumber,
8724                              lastLoadGameTitle, lastLoadGameUseList);
8725     } else {
8726         return LoadGame(lastLoadGameFP, gameNumber,
8727                         lastLoadGameTitle, lastLoadGameUseList);
8728     }
8729 }
8730
8731
8732
8733 /* Load the nth game from open file f */
8734 int
8735 LoadGame(f, gameNumber, title, useList)
8736      FILE *f;
8737      int gameNumber;
8738      char *title;
8739      int useList;
8740 {
8741     ChessMove cm;
8742     char buf[MSG_SIZ];
8743     int gn = gameNumber;
8744     ListGame *lg = NULL;
8745     int numPGNTags = 0;
8746     int err;
8747     GameMode oldGameMode;
8748     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8749
8750     if (appData.debugMode) 
8751         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8752
8753     if (gameMode == Training )
8754         SetTrainingModeOff();
8755
8756     oldGameMode = gameMode;
8757     if (gameMode != BeginningOfGame) {
8758       Reset(FALSE, TRUE);
8759     }
8760
8761     gameFileFP = f;
8762     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8763         fclose(lastLoadGameFP);
8764     }
8765
8766     if (useList) {
8767         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8768         
8769         if (lg) {
8770             fseek(f, lg->offset, 0);
8771             GameListHighlight(gameNumber);
8772             gn = 1;
8773         }
8774         else {
8775             DisplayError(_("Game number out of range"), 0);
8776             return FALSE;
8777         }
8778     } else {
8779         GameListDestroy();
8780         if (fseek(f, 0, 0) == -1) {
8781             if (f == lastLoadGameFP ?
8782                 gameNumber == lastLoadGameNumber + 1 :
8783                 gameNumber == 1) {
8784                 gn = 1;
8785             } else {
8786                 DisplayError(_("Can't seek on game file"), 0);
8787                 return FALSE;
8788             }
8789         }
8790     }
8791     lastLoadGameFP = f;
8792     lastLoadGameNumber = gameNumber;
8793     strcpy(lastLoadGameTitle, title);
8794     lastLoadGameUseList = useList;
8795
8796     yynewfile(f);
8797
8798     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8799       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8800                 lg->gameInfo.black);
8801             DisplayTitle(buf);
8802     } else if (*title != NULLCHAR) {
8803         if (gameNumber > 1) {
8804             sprintf(buf, "%s %d", title, gameNumber);
8805             DisplayTitle(buf);
8806         } else {
8807             DisplayTitle(title);
8808         }
8809     }
8810
8811     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8812         gameMode = PlayFromGameFile;
8813         ModeHighlight();
8814     }
8815
8816     currentMove = forwardMostMove = backwardMostMove = 0;
8817     CopyBoard(boards[0], initialPosition);
8818     StopClocks();
8819
8820     /*
8821      * Skip the first gn-1 games in the file.
8822      * Also skip over anything that precedes an identifiable 
8823      * start of game marker, to avoid being confused by 
8824      * garbage at the start of the file.  Currently 
8825      * recognized start of game markers are the move number "1",
8826      * the pattern "gnuchess .* game", the pattern
8827      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8828      * A game that starts with one of the latter two patterns
8829      * will also have a move number 1, possibly
8830      * following a position diagram.
8831      * 5-4-02: Let's try being more lenient and allowing a game to
8832      * start with an unnumbered move.  Does that break anything?
8833      */
8834     cm = lastLoadGameStart = (ChessMove) 0;
8835     while (gn > 0) {
8836         yyboardindex = forwardMostMove;
8837         cm = (ChessMove) yylex();
8838         switch (cm) {
8839           case (ChessMove) 0:
8840             if (cmailMsgLoaded) {
8841                 nCmailGames = CMAIL_MAX_GAMES - gn;
8842             } else {
8843                 Reset(TRUE, TRUE);
8844                 DisplayError(_("Game not found in file"), 0);
8845             }
8846             return FALSE;
8847
8848           case GNUChessGame:
8849           case XBoardGame:
8850             gn--;
8851             lastLoadGameStart = cm;
8852             break;
8853             
8854           case MoveNumberOne:
8855             switch (lastLoadGameStart) {
8856               case GNUChessGame:
8857               case XBoardGame:
8858               case PGNTag:
8859                 break;
8860               case MoveNumberOne:
8861               case (ChessMove) 0:
8862                 gn--;           /* count this game */
8863                 lastLoadGameStart = cm;
8864                 break;
8865               default:
8866                 /* impossible */
8867                 break;
8868             }
8869             break;
8870
8871           case PGNTag:
8872             switch (lastLoadGameStart) {
8873               case GNUChessGame:
8874               case PGNTag:
8875               case MoveNumberOne:
8876               case (ChessMove) 0:
8877                 gn--;           /* count this game */
8878                 lastLoadGameStart = cm;
8879                 break;
8880               case XBoardGame:
8881                 lastLoadGameStart = cm; /* game counted already */
8882                 break;
8883               default:
8884                 /* impossible */
8885                 break;
8886             }
8887             if (gn > 0) {
8888                 do {
8889                     yyboardindex = forwardMostMove;
8890                     cm = (ChessMove) yylex();
8891                 } while (cm == PGNTag || cm == Comment);
8892             }
8893             break;
8894
8895           case WhiteWins:
8896           case BlackWins:
8897           case GameIsDrawn:
8898             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8899                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8900                     != CMAIL_OLD_RESULT) {
8901                     nCmailResults ++ ;
8902                     cmailResult[  CMAIL_MAX_GAMES
8903                                 - gn - 1] = CMAIL_OLD_RESULT;
8904                 }
8905             }
8906             break;
8907
8908           case NormalMove:
8909             /* Only a NormalMove can be at the start of a game
8910              * without a position diagram. */
8911             if (lastLoadGameStart == (ChessMove) 0) {
8912               gn--;
8913               lastLoadGameStart = MoveNumberOne;
8914             }
8915             break;
8916
8917           default:
8918             break;
8919         }
8920     }
8921     
8922     if (appData.debugMode)
8923       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8924
8925     if (cm == XBoardGame) {
8926         /* Skip any header junk before position diagram and/or move 1 */
8927         for (;;) {
8928             yyboardindex = forwardMostMove;
8929             cm = (ChessMove) yylex();
8930
8931             if (cm == (ChessMove) 0 ||
8932                 cm == GNUChessGame || cm == XBoardGame) {
8933                 /* Empty game; pretend end-of-file and handle later */
8934                 cm = (ChessMove) 0;
8935                 break;
8936             }
8937
8938             if (cm == MoveNumberOne || cm == PositionDiagram ||
8939                 cm == PGNTag || cm == Comment)
8940               break;
8941         }
8942     } else if (cm == GNUChessGame) {
8943         if (gameInfo.event != NULL) {
8944             free(gameInfo.event);
8945         }
8946         gameInfo.event = StrSave(yy_text);
8947     }   
8948
8949     startedFromSetupPosition = FALSE;
8950     while (cm == PGNTag) {
8951         if (appData.debugMode) 
8952           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8953         err = ParsePGNTag(yy_text, &gameInfo);
8954         if (!err) numPGNTags++;
8955
8956         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8957         if(gameInfo.variant != oldVariant) {
8958             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8959             InitPosition(TRUE);
8960             oldVariant = gameInfo.variant;
8961             if (appData.debugMode) 
8962               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8963         }
8964
8965
8966         if (gameInfo.fen != NULL) {
8967           Board initial_position;
8968           startedFromSetupPosition = TRUE;
8969           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8970             Reset(TRUE, TRUE);
8971             DisplayError(_("Bad FEN position in file"), 0);
8972             return FALSE;
8973           }
8974           CopyBoard(boards[0], initial_position);
8975           if (blackPlaysFirst) {
8976             currentMove = forwardMostMove = backwardMostMove = 1;
8977             CopyBoard(boards[1], initial_position);
8978             strcpy(moveList[0], "");
8979             strcpy(parseList[0], "");
8980             timeRemaining[0][1] = whiteTimeRemaining;
8981             timeRemaining[1][1] = blackTimeRemaining;
8982             if (commentList[0] != NULL) {
8983               commentList[1] = commentList[0];
8984               commentList[0] = NULL;
8985             }
8986           } else {
8987             currentMove = forwardMostMove = backwardMostMove = 0;
8988           }
8989           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8990           {   int i;
8991               initialRulePlies = FENrulePlies;
8992               epStatus[forwardMostMove] = FENepStatus;
8993               for( i=0; i< nrCastlingRights; i++ )
8994                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8995           }
8996           yyboardindex = forwardMostMove;
8997           free(gameInfo.fen);
8998           gameInfo.fen = NULL;
8999         }
9000
9001         yyboardindex = forwardMostMove;
9002         cm = (ChessMove) yylex();
9003
9004         /* Handle comments interspersed among the tags */
9005         while (cm == Comment) {
9006             char *p;
9007             if (appData.debugMode) 
9008               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9009             p = yy_text;
9010             if (*p == '{' || *p == '[' || *p == '(') {
9011                 p[strlen(p) - 1] = NULLCHAR;
9012                 p++;
9013             }
9014             while (*p == '\n') p++;
9015             AppendComment(currentMove, p);
9016             yyboardindex = forwardMostMove;
9017             cm = (ChessMove) yylex();
9018         }
9019     }
9020
9021     /* don't rely on existence of Event tag since if game was
9022      * pasted from clipboard the Event tag may not exist
9023      */
9024     if (numPGNTags > 0){
9025         char *tags;
9026         if (gameInfo.variant == VariantNormal) {
9027           gameInfo.variant = StringToVariant(gameInfo.event);
9028         }
9029         if (!matchMode) {
9030           if( appData.autoDisplayTags ) {
9031             tags = PGNTags(&gameInfo);
9032             TagsPopUp(tags, CmailMsg());
9033             free(tags);
9034           }
9035         }
9036     } else {
9037         /* Make something up, but don't display it now */
9038         SetGameInfo();
9039         TagsPopDown();
9040     }
9041
9042     if (cm == PositionDiagram) {
9043         int i, j;
9044         char *p;
9045         Board initial_position;
9046
9047         if (appData.debugMode)
9048           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9049
9050         if (!startedFromSetupPosition) {
9051             p = yy_text;
9052             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9053               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9054                 switch (*p) {
9055                   case '[':
9056                   case '-':
9057                   case ' ':
9058                   case '\t':
9059                   case '\n':
9060                   case '\r':
9061                     break;
9062                   default:
9063                     initial_position[i][j++] = CharToPiece(*p);
9064                     break;
9065                 }
9066             while (*p == ' ' || *p == '\t' ||
9067                    *p == '\n' || *p == '\r') p++;
9068         
9069             if (strncmp(p, "black", strlen("black"))==0)
9070               blackPlaysFirst = TRUE;
9071             else
9072               blackPlaysFirst = FALSE;
9073             startedFromSetupPosition = TRUE;
9074         
9075             CopyBoard(boards[0], initial_position);
9076             if (blackPlaysFirst) {
9077                 currentMove = forwardMostMove = backwardMostMove = 1;
9078                 CopyBoard(boards[1], initial_position);
9079                 strcpy(moveList[0], "");
9080                 strcpy(parseList[0], "");
9081                 timeRemaining[0][1] = whiteTimeRemaining;
9082                 timeRemaining[1][1] = blackTimeRemaining;
9083                 if (commentList[0] != NULL) {
9084                     commentList[1] = commentList[0];
9085                     commentList[0] = NULL;
9086                 }
9087             } else {
9088                 currentMove = forwardMostMove = backwardMostMove = 0;
9089             }
9090         }
9091         yyboardindex = forwardMostMove;
9092         cm = (ChessMove) yylex();
9093     }
9094
9095     if (first.pr == NoProc) {
9096         StartChessProgram(&first);
9097     }
9098     InitChessProgram(&first, FALSE);
9099     SendToProgram("force\n", &first);
9100     if (startedFromSetupPosition) {
9101         SendBoard(&first, forwardMostMove);
9102     if (appData.debugMode) {
9103         fprintf(debugFP, "Load Game\n");
9104     }
9105         DisplayBothClocks();
9106     }      
9107
9108     /* [HGM] server: flag to write setup moves in broadcast file as one */
9109     loadFlag = appData.suppressLoadMoves;
9110
9111     while (cm == Comment) {
9112         char *p;
9113         if (appData.debugMode) 
9114           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9115         p = yy_text;
9116         if (*p == '{' || *p == '[' || *p == '(') {
9117             p[strlen(p) - 1] = NULLCHAR;
9118             p++;
9119         }
9120         while (*p == '\n') p++;
9121         AppendComment(currentMove, p);
9122         yyboardindex = forwardMostMove;
9123         cm = (ChessMove) yylex();
9124     }
9125
9126     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9127         cm == WhiteWins || cm == BlackWins ||
9128         cm == GameIsDrawn || cm == GameUnfinished) {
9129         DisplayMessage("", _("No moves in game"));
9130         if (cmailMsgLoaded) {
9131             if (appData.debugMode)
9132               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9133             ClearHighlights();
9134             flipView = FALSE;
9135         }
9136         DrawPosition(FALSE, boards[currentMove]);
9137         DisplayBothClocks();
9138         gameMode = EditGame;
9139         ModeHighlight();
9140         gameFileFP = NULL;
9141         cmailOldMove = 0;
9142         return TRUE;
9143     }
9144
9145     // [HGM] PV info: routine tests if comment empty
9146     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9147         DisplayComment(currentMove - 1, commentList[currentMove]);
9148     }
9149     if (!matchMode && appData.timeDelay != 0) 
9150       DrawPosition(FALSE, boards[currentMove]);
9151
9152     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9153       programStats.ok_to_send = 1;
9154     }
9155
9156     /* if the first token after the PGN tags is a move
9157      * and not move number 1, retrieve it from the parser 
9158      */
9159     if (cm != MoveNumberOne)
9160         LoadGameOneMove(cm);
9161
9162     /* load the remaining moves from the file */
9163     while (LoadGameOneMove((ChessMove)0)) {
9164       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9165       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9166     }
9167
9168     /* rewind to the start of the game */
9169     currentMove = backwardMostMove;
9170
9171     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9172
9173     if (oldGameMode == AnalyzeFile ||
9174         oldGameMode == AnalyzeMode) {
9175       AnalyzeFileEvent();
9176     }
9177
9178     if (matchMode || appData.timeDelay == 0) {
9179       ToEndEvent();
9180       gameMode = EditGame;
9181       ModeHighlight();
9182     } else if (appData.timeDelay > 0) {
9183       AutoPlayGameLoop();
9184     }
9185
9186     if (appData.debugMode) 
9187         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9188
9189     loadFlag = 0; /* [HGM] true game starts */
9190     return TRUE;
9191 }
9192
9193 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9194 int
9195 ReloadPosition(offset)
9196      int offset;
9197 {
9198     int positionNumber = lastLoadPositionNumber + offset;
9199     if (lastLoadPositionFP == NULL) {
9200         DisplayError(_("No position has been loaded yet"), 0);
9201         return FALSE;
9202     }
9203     if (positionNumber <= 0) {
9204         DisplayError(_("Can't back up any further"), 0);
9205         return FALSE;
9206     }
9207     return LoadPosition(lastLoadPositionFP, positionNumber,
9208                         lastLoadPositionTitle);
9209 }
9210
9211 /* Load the nth position from the given file */
9212 int
9213 LoadPositionFromFile(filename, n, title)
9214      char *filename;
9215      int n;
9216      char *title;
9217 {
9218     FILE *f;
9219     char buf[MSG_SIZ];
9220
9221     if (strcmp(filename, "-") == 0) {
9222         return LoadPosition(stdin, n, "stdin");
9223     } else {
9224         f = fopen(filename, "rb");
9225         if (f == NULL) {
9226             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9227             DisplayError(buf, errno);
9228             return FALSE;
9229         } else {
9230             return LoadPosition(f, n, title);
9231         }
9232     }
9233 }
9234
9235 /* Load the nth position from the given open file, and close it */
9236 int
9237 LoadPosition(f, positionNumber, title)
9238      FILE *f;
9239      int positionNumber;
9240      char *title;
9241 {
9242     char *p, line[MSG_SIZ];
9243     Board initial_position;
9244     int i, j, fenMode, pn;
9245     
9246     if (gameMode == Training )
9247         SetTrainingModeOff();
9248
9249     if (gameMode != BeginningOfGame) {
9250         Reset(FALSE, TRUE);
9251     }
9252     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9253         fclose(lastLoadPositionFP);
9254     }
9255     if (positionNumber == 0) positionNumber = 1;
9256     lastLoadPositionFP = f;
9257     lastLoadPositionNumber = positionNumber;
9258     strcpy(lastLoadPositionTitle, title);
9259     if (first.pr == NoProc) {
9260       StartChessProgram(&first);
9261       InitChessProgram(&first, FALSE);
9262     }    
9263     pn = positionNumber;
9264     if (positionNumber < 0) {
9265         /* Negative position number means to seek to that byte offset */
9266         if (fseek(f, -positionNumber, 0) == -1) {
9267             DisplayError(_("Can't seek on position file"), 0);
9268             return FALSE;
9269         };
9270         pn = 1;
9271     } else {
9272         if (fseek(f, 0, 0) == -1) {
9273             if (f == lastLoadPositionFP ?
9274                 positionNumber == lastLoadPositionNumber + 1 :
9275                 positionNumber == 1) {
9276                 pn = 1;
9277             } else {
9278                 DisplayError(_("Can't seek on position file"), 0);
9279                 return FALSE;
9280             }
9281         }
9282     }
9283     /* See if this file is FEN or old-style xboard */
9284     if (fgets(line, MSG_SIZ, f) == NULL) {
9285         DisplayError(_("Position not found in file"), 0);
9286         return FALSE;
9287     }
9288     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9289     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9290
9291     if (pn >= 2) {
9292         if (fenMode || line[0] == '#') pn--;
9293         while (pn > 0) {
9294             /* skip positions before number pn */
9295             if (fgets(line, MSG_SIZ, f) == NULL) {
9296                 Reset(TRUE, TRUE);
9297                 DisplayError(_("Position not found in file"), 0);
9298                 return FALSE;
9299             }
9300             if (fenMode || line[0] == '#') pn--;
9301         }
9302     }
9303
9304     if (fenMode) {
9305         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9306             DisplayError(_("Bad FEN position in file"), 0);
9307             return FALSE;
9308         }
9309     } else {
9310         (void) fgets(line, MSG_SIZ, f);
9311         (void) fgets(line, MSG_SIZ, f);
9312     
9313         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9314             (void) fgets(line, MSG_SIZ, f);
9315             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9316                 if (*p == ' ')
9317                   continue;
9318                 initial_position[i][j++] = CharToPiece(*p);
9319             }
9320         }
9321     
9322         blackPlaysFirst = FALSE;
9323         if (!feof(f)) {
9324             (void) fgets(line, MSG_SIZ, f);
9325             if (strncmp(line, "black", strlen("black"))==0)
9326               blackPlaysFirst = TRUE;
9327         }
9328     }
9329     startedFromSetupPosition = TRUE;
9330     
9331     SendToProgram("force\n", &first);
9332     CopyBoard(boards[0], initial_position);
9333     if (blackPlaysFirst) {
9334         currentMove = forwardMostMove = backwardMostMove = 1;
9335         strcpy(moveList[0], "");
9336         strcpy(parseList[0], "");
9337         CopyBoard(boards[1], initial_position);
9338         DisplayMessage("", _("Black to play"));
9339     } else {
9340         currentMove = forwardMostMove = backwardMostMove = 0;
9341         DisplayMessage("", _("White to play"));
9342     }
9343           /* [HGM] copy FEN attributes as well */
9344           {   int i;
9345               initialRulePlies = FENrulePlies;
9346               epStatus[forwardMostMove] = FENepStatus;
9347               for( i=0; i< nrCastlingRights; i++ )
9348                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9349           }
9350     SendBoard(&first, forwardMostMove);
9351     if (appData.debugMode) {
9352 int i, j;
9353   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9354   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9355         fprintf(debugFP, "Load Position\n");
9356     }
9357
9358     if (positionNumber > 1) {
9359         sprintf(line, "%s %d", title, positionNumber);
9360         DisplayTitle(line);
9361     } else {
9362         DisplayTitle(title);
9363     }
9364     gameMode = EditGame;
9365     ModeHighlight();
9366     ResetClocks();
9367     timeRemaining[0][1] = whiteTimeRemaining;
9368     timeRemaining[1][1] = blackTimeRemaining;
9369     DrawPosition(FALSE, boards[currentMove]);
9370    
9371     return TRUE;
9372 }
9373
9374
9375 void
9376 CopyPlayerNameIntoFileName(dest, src)
9377      char **dest, *src;
9378 {
9379     while (*src != NULLCHAR && *src != ',') {
9380         if (*src == ' ') {
9381             *(*dest)++ = '_';
9382             src++;
9383         } else {
9384             *(*dest)++ = *src++;
9385         }
9386     }
9387 }
9388
9389 char *DefaultFileName(ext)
9390      char *ext;
9391 {
9392     static char def[MSG_SIZ];
9393     char *p;
9394
9395     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9396         p = def;
9397         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9398         *p++ = '-';
9399         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9400         *p++ = '.';
9401         strcpy(p, ext);
9402     } else {
9403         def[0] = NULLCHAR;
9404     }
9405     return def;
9406 }
9407
9408 /* Save the current game to the given file */
9409 int
9410 SaveGameToFile(filename, append)
9411      char *filename;
9412      int append;
9413 {
9414     FILE *f;
9415     char buf[MSG_SIZ];
9416
9417     if (strcmp(filename, "-") == 0) {
9418         return SaveGame(stdout, 0, NULL);
9419     } else {
9420         f = fopen(filename, append ? "a" : "w");
9421         if (f == NULL) {
9422             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9423             DisplayError(buf, errno);
9424             return FALSE;
9425         } else {
9426             return SaveGame(f, 0, NULL);
9427         }
9428     }
9429 }
9430
9431 char *
9432 SavePart(str)
9433      char *str;
9434 {
9435     static char buf[MSG_SIZ];
9436     char *p;
9437     
9438     p = strchr(str, ' ');
9439     if (p == NULL) return str;
9440     strncpy(buf, str, p - str);
9441     buf[p - str] = NULLCHAR;
9442     return buf;
9443 }
9444
9445 #define PGN_MAX_LINE 75
9446
9447 #define PGN_SIDE_WHITE  0
9448 #define PGN_SIDE_BLACK  1
9449
9450 /* [AS] */
9451 static int FindFirstMoveOutOfBook( int side )
9452 {
9453     int result = -1;
9454
9455     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9456         int index = backwardMostMove;
9457         int has_book_hit = 0;
9458
9459         if( (index % 2) != side ) {
9460             index++;
9461         }
9462
9463         while( index < forwardMostMove ) {
9464             /* Check to see if engine is in book */
9465             int depth = pvInfoList[index].depth;
9466             int score = pvInfoList[index].score;
9467             int in_book = 0;
9468
9469             if( depth <= 2 ) {
9470                 in_book = 1;
9471             }
9472             else if( score == 0 && depth == 63 ) {
9473                 in_book = 1; /* Zappa */
9474             }
9475             else if( score == 2 && depth == 99 ) {
9476                 in_book = 1; /* Abrok */
9477             }
9478
9479             has_book_hit += in_book;
9480
9481             if( ! in_book ) {
9482                 result = index;
9483
9484                 break;
9485             }
9486
9487             index += 2;
9488         }
9489     }
9490
9491     return result;
9492 }
9493
9494 /* [AS] */
9495 void GetOutOfBookInfo( char * buf )
9496 {
9497     int oob[2];
9498     int i;
9499     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9500
9501     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9502     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9503
9504     *buf = '\0';
9505
9506     if( oob[0] >= 0 || oob[1] >= 0 ) {
9507         for( i=0; i<2; i++ ) {
9508             int idx = oob[i];
9509
9510             if( idx >= 0 ) {
9511                 if( i > 0 && oob[0] >= 0 ) {
9512                     strcat( buf, "   " );
9513                 }
9514
9515                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9516                 sprintf( buf+strlen(buf), "%s%.2f", 
9517                     pvInfoList[idx].score >= 0 ? "+" : "",
9518                     pvInfoList[idx].score / 100.0 );
9519             }
9520         }
9521     }
9522 }
9523
9524 /* Save game in PGN style and close the file */
9525 int
9526 SaveGamePGN(f)
9527      FILE *f;
9528 {
9529     int i, offset, linelen, newblock;
9530     time_t tm;
9531 //    char *movetext;
9532     char numtext[32];
9533     int movelen, numlen, blank;
9534     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9535
9536     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9537     
9538     tm = time((time_t *) NULL);
9539     
9540     PrintPGNTags(f, &gameInfo);
9541     
9542     if (backwardMostMove > 0 || startedFromSetupPosition) {
9543         char *fen = PositionToFEN(backwardMostMove, NULL);
9544         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9545         fprintf(f, "\n{--------------\n");
9546         PrintPosition(f, backwardMostMove);
9547         fprintf(f, "--------------}\n");
9548         free(fen);
9549     }
9550     else {
9551         /* [AS] Out of book annotation */
9552         if( appData.saveOutOfBookInfo ) {
9553             char buf[64];
9554
9555             GetOutOfBookInfo( buf );
9556
9557             if( buf[0] != '\0' ) {
9558                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9559             }
9560         }
9561
9562         fprintf(f, "\n");
9563     }
9564
9565     i = backwardMostMove;
9566     linelen = 0;
9567     newblock = TRUE;
9568
9569     while (i < forwardMostMove) {
9570         /* Print comments preceding this move */
9571         if (commentList[i] != NULL) {
9572             if (linelen > 0) fprintf(f, "\n");
9573             fprintf(f, "{\n%s}\n", commentList[i]);
9574             linelen = 0;
9575             newblock = TRUE;
9576         }
9577
9578         /* Format move number */
9579         if ((i % 2) == 0) {
9580             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9581         } else {
9582             if (newblock) {
9583                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9584             } else {
9585                 numtext[0] = NULLCHAR;
9586             }
9587         }
9588         numlen = strlen(numtext);
9589         newblock = FALSE;
9590
9591         /* Print move number */
9592         blank = linelen > 0 && numlen > 0;
9593         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9594             fprintf(f, "\n");
9595             linelen = 0;
9596             blank = 0;
9597         }
9598         if (blank) {
9599             fprintf(f, " ");
9600             linelen++;
9601         }
9602         fprintf(f, numtext);
9603         linelen += numlen;
9604
9605         /* Get move */
9606         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9607         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9608
9609         /* Print move */
9610         blank = linelen > 0 && movelen > 0;
9611         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9612             fprintf(f, "\n");
9613             linelen = 0;
9614             blank = 0;
9615         }
9616         if (blank) {
9617             fprintf(f, " ");
9618             linelen++;
9619         }
9620         fprintf(f, move_buffer);
9621         linelen += movelen;
9622
9623         /* [AS] Add PV info if present */
9624         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9625             /* [HGM] add time */
9626             char buf[MSG_SIZ]; int seconds = 0;
9627
9628             if(i >= backwardMostMove) {
9629                 if(WhiteOnMove(i))
9630                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9631                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9632                 else
9633                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9634                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9635             }
9636             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9637
9638             if( seconds <= 0) buf[0] = 0; else
9639             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9640                 seconds = (seconds + 4)/10; // round to full seconds
9641                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9642                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9643             }
9644
9645             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9646                 pvInfoList[i].score >= 0 ? "+" : "",
9647                 pvInfoList[i].score / 100.0,
9648                 pvInfoList[i].depth,
9649                 buf );
9650
9651             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9652
9653             /* Print score/depth */
9654             blank = linelen > 0 && movelen > 0;
9655             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9656                 fprintf(f, "\n");
9657                 linelen = 0;
9658                 blank = 0;
9659             }
9660             if (blank) {
9661                 fprintf(f, " ");
9662                 linelen++;
9663             }
9664             fprintf(f, move_buffer);
9665             linelen += movelen;
9666         }
9667
9668         i++;
9669     }
9670     
9671     /* Start a new line */
9672     if (linelen > 0) fprintf(f, "\n");
9673
9674     /* Print comments after last move */
9675     if (commentList[i] != NULL) {
9676         fprintf(f, "{\n%s}\n", commentList[i]);
9677     }
9678
9679     /* Print result */
9680     if (gameInfo.resultDetails != NULL &&
9681         gameInfo.resultDetails[0] != NULLCHAR) {
9682         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9683                 PGNResult(gameInfo.result));
9684     } else {
9685         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9686     }
9687
9688     fclose(f);
9689     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9690     return TRUE;
9691 }
9692
9693 /* Save game in old style and close the file */
9694 int
9695 SaveGameOldStyle(f)
9696      FILE *f;
9697 {
9698     int i, offset;
9699     time_t tm;
9700     
9701     tm = time((time_t *) NULL);
9702     
9703     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9704     PrintOpponents(f);
9705     
9706     if (backwardMostMove > 0 || startedFromSetupPosition) {
9707         fprintf(f, "\n[--------------\n");
9708         PrintPosition(f, backwardMostMove);
9709         fprintf(f, "--------------]\n");
9710     } else {
9711         fprintf(f, "\n");
9712     }
9713
9714     i = backwardMostMove;
9715     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9716
9717     while (i < forwardMostMove) {
9718         if (commentList[i] != NULL) {
9719             fprintf(f, "[%s]\n", commentList[i]);
9720         }
9721
9722         if ((i % 2) == 1) {
9723             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9724             i++;
9725         } else {
9726             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9727             i++;
9728             if (commentList[i] != NULL) {
9729                 fprintf(f, "\n");
9730                 continue;
9731             }
9732             if (i >= forwardMostMove) {
9733                 fprintf(f, "\n");
9734                 break;
9735             }
9736             fprintf(f, "%s\n", parseList[i]);
9737             i++;
9738         }
9739     }
9740     
9741     if (commentList[i] != NULL) {
9742         fprintf(f, "[%s]\n", commentList[i]);
9743     }
9744
9745     /* This isn't really the old style, but it's close enough */
9746     if (gameInfo.resultDetails != NULL &&
9747         gameInfo.resultDetails[0] != NULLCHAR) {
9748         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9749                 gameInfo.resultDetails);
9750     } else {
9751         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9752     }
9753
9754     fclose(f);
9755     return TRUE;
9756 }
9757
9758 /* Save the current game to open file f and close the file */
9759 int
9760 SaveGame(f, dummy, dummy2)
9761      FILE *f;
9762      int dummy;
9763      char *dummy2;
9764 {
9765     if (gameMode == EditPosition) EditPositionDone();
9766     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9767     if (appData.oldSaveStyle)
9768       return SaveGameOldStyle(f);
9769     else
9770       return SaveGamePGN(f);
9771 }
9772
9773 /* Save the current position to the given file */
9774 int
9775 SavePositionToFile(filename)
9776      char *filename;
9777 {
9778     FILE *f;
9779     char buf[MSG_SIZ];
9780
9781     if (strcmp(filename, "-") == 0) {
9782         return SavePosition(stdout, 0, NULL);
9783     } else {
9784         f = fopen(filename, "a");
9785         if (f == NULL) {
9786             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9787             DisplayError(buf, errno);
9788             return FALSE;
9789         } else {
9790             SavePosition(f, 0, NULL);
9791             return TRUE;
9792         }
9793     }
9794 }
9795
9796 /* Save the current position to the given open file and close the file */
9797 int
9798 SavePosition(f, dummy, dummy2)
9799      FILE *f;
9800      int dummy;
9801      char *dummy2;
9802 {
9803     time_t tm;
9804     char *fen;
9805     
9806     if (appData.oldSaveStyle) {
9807         tm = time((time_t *) NULL);
9808     
9809         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9810         PrintOpponents(f);
9811         fprintf(f, "[--------------\n");
9812         PrintPosition(f, currentMove);
9813         fprintf(f, "--------------]\n");
9814     } else {
9815         fen = PositionToFEN(currentMove, NULL);
9816         fprintf(f, "%s\n", fen);
9817         free(fen);
9818     }
9819     fclose(f);
9820     return TRUE;
9821 }
9822
9823 void
9824 ReloadCmailMsgEvent(unregister)
9825      int unregister;
9826 {
9827 #if !WIN32
9828     static char *inFilename = NULL;
9829     static char *outFilename;
9830     int i;
9831     struct stat inbuf, outbuf;
9832     int status;
9833     
9834     /* Any registered moves are unregistered if unregister is set, */
9835     /* i.e. invoked by the signal handler */
9836     if (unregister) {
9837         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9838             cmailMoveRegistered[i] = FALSE;
9839             if (cmailCommentList[i] != NULL) {
9840                 free(cmailCommentList[i]);
9841                 cmailCommentList[i] = NULL;
9842             }
9843         }
9844         nCmailMovesRegistered = 0;
9845     }
9846
9847     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9848         cmailResult[i] = CMAIL_NOT_RESULT;
9849     }
9850     nCmailResults = 0;
9851
9852     if (inFilename == NULL) {
9853         /* Because the filenames are static they only get malloced once  */
9854         /* and they never get freed                                      */
9855         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9856         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9857
9858         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9859         sprintf(outFilename, "%s.out", appData.cmailGameName);
9860     }
9861     
9862     status = stat(outFilename, &outbuf);
9863     if (status < 0) {
9864         cmailMailedMove = FALSE;
9865     } else {
9866         status = stat(inFilename, &inbuf);
9867         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9868     }
9869     
9870     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9871        counts the games, notes how each one terminated, etc.
9872        
9873        It would be nice to remove this kludge and instead gather all
9874        the information while building the game list.  (And to keep it
9875        in the game list nodes instead of having a bunch of fixed-size
9876        parallel arrays.)  Note this will require getting each game's
9877        termination from the PGN tags, as the game list builder does
9878        not process the game moves.  --mann
9879        */
9880     cmailMsgLoaded = TRUE;
9881     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9882     
9883     /* Load first game in the file or popup game menu */
9884     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9885
9886 #endif /* !WIN32 */
9887     return;
9888 }
9889
9890 int
9891 RegisterMove()
9892 {
9893     FILE *f;
9894     char string[MSG_SIZ];
9895
9896     if (   cmailMailedMove
9897         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9898         return TRUE;            /* Allow free viewing  */
9899     }
9900
9901     /* Unregister move to ensure that we don't leave RegisterMove        */
9902     /* with the move registered when the conditions for registering no   */
9903     /* longer hold                                                       */
9904     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9905         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9906         nCmailMovesRegistered --;
9907
9908         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9909           {
9910               free(cmailCommentList[lastLoadGameNumber - 1]);
9911               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9912           }
9913     }
9914
9915     if (cmailOldMove == -1) {
9916         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9917         return FALSE;
9918     }
9919
9920     if (currentMove > cmailOldMove + 1) {
9921         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9922         return FALSE;
9923     }
9924
9925     if (currentMove < cmailOldMove) {
9926         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9927         return FALSE;
9928     }
9929
9930     if (forwardMostMove > currentMove) {
9931         /* Silently truncate extra moves */
9932         TruncateGame();
9933     }
9934
9935     if (   (currentMove == cmailOldMove + 1)
9936         || (   (currentMove == cmailOldMove)
9937             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9938                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9939         if (gameInfo.result != GameUnfinished) {
9940             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9941         }
9942
9943         if (commentList[currentMove] != NULL) {
9944             cmailCommentList[lastLoadGameNumber - 1]
9945               = StrSave(commentList[currentMove]);
9946         }
9947         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9948
9949         if (appData.debugMode)
9950           fprintf(debugFP, "Saving %s for game %d\n",
9951                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9952
9953         sprintf(string,
9954                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9955         
9956         f = fopen(string, "w");
9957         if (appData.oldSaveStyle) {
9958             SaveGameOldStyle(f); /* also closes the file */
9959             
9960             sprintf(string, "%s.pos.out", appData.cmailGameName);
9961             f = fopen(string, "w");
9962             SavePosition(f, 0, NULL); /* also closes the file */
9963         } else {
9964             fprintf(f, "{--------------\n");
9965             PrintPosition(f, currentMove);
9966             fprintf(f, "--------------}\n\n");
9967             
9968             SaveGame(f, 0, NULL); /* also closes the file*/
9969         }
9970         
9971         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9972         nCmailMovesRegistered ++;
9973     } else if (nCmailGames == 1) {
9974         DisplayError(_("You have not made a move yet"), 0);
9975         return FALSE;
9976     }
9977
9978     return TRUE;
9979 }
9980
9981 void
9982 MailMoveEvent()
9983 {
9984 #if !WIN32
9985     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9986     FILE *commandOutput;
9987     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9988     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9989     int nBuffers;
9990     int i;
9991     int archived;
9992     char *arcDir;
9993
9994     if (! cmailMsgLoaded) {
9995         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9996         return;
9997     }
9998
9999     if (nCmailGames == nCmailResults) {
10000         DisplayError(_("No unfinished games"), 0);
10001         return;
10002     }
10003
10004 #if CMAIL_PROHIBIT_REMAIL
10005     if (cmailMailedMove) {
10006         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);
10007         DisplayError(msg, 0);
10008         return;
10009     }
10010 #endif
10011
10012     if (! (cmailMailedMove || RegisterMove())) return;
10013     
10014     if (   cmailMailedMove
10015         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10016         sprintf(string, partCommandString,
10017                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10018         commandOutput = popen(string, "r");
10019
10020         if (commandOutput == NULL) {
10021             DisplayError(_("Failed to invoke cmail"), 0);
10022         } else {
10023             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10024                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10025             }
10026             if (nBuffers > 1) {
10027                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10028                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10029                 nBytes = MSG_SIZ - 1;
10030             } else {
10031                 (void) memcpy(msg, buffer, nBytes);
10032             }
10033             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10034
10035             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10036                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10037
10038                 archived = TRUE;
10039                 for (i = 0; i < nCmailGames; i ++) {
10040                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10041                         archived = FALSE;
10042                     }
10043                 }
10044                 if (   archived
10045                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10046                         != NULL)) {
10047                     sprintf(buffer, "%s/%s.%s.archive",
10048                             arcDir,
10049                             appData.cmailGameName,
10050                             gameInfo.date);
10051                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10052                     cmailMsgLoaded = FALSE;
10053                 }
10054             }
10055
10056             DisplayInformation(msg);
10057             pclose(commandOutput);
10058         }
10059     } else {
10060         if ((*cmailMsg) != '\0') {
10061             DisplayInformation(cmailMsg);
10062         }
10063     }
10064
10065     return;
10066 #endif /* !WIN32 */
10067 }
10068
10069 char *
10070 CmailMsg()
10071 {
10072 #if WIN32
10073     return NULL;
10074 #else
10075     int  prependComma = 0;
10076     char number[5];
10077     char string[MSG_SIZ];       /* Space for game-list */
10078     int  i;
10079     
10080     if (!cmailMsgLoaded) return "";
10081
10082     if (cmailMailedMove) {
10083         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10084     } else {
10085         /* Create a list of games left */
10086         sprintf(string, "[");
10087         for (i = 0; i < nCmailGames; i ++) {
10088             if (! (   cmailMoveRegistered[i]
10089                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10090                 if (prependComma) {
10091                     sprintf(number, ",%d", i + 1);
10092                 } else {
10093                     sprintf(number, "%d", i + 1);
10094                     prependComma = 1;
10095                 }
10096                 
10097                 strcat(string, number);
10098             }
10099         }
10100         strcat(string, "]");
10101
10102         if (nCmailMovesRegistered + nCmailResults == 0) {
10103             switch (nCmailGames) {
10104               case 1:
10105                 sprintf(cmailMsg,
10106                         _("Still need to make move for game\n"));
10107                 break;
10108                 
10109               case 2:
10110                 sprintf(cmailMsg,
10111                         _("Still need to make moves for both games\n"));
10112                 break;
10113                 
10114               default:
10115                 sprintf(cmailMsg,
10116                         _("Still need to make moves for all %d games\n"),
10117                         nCmailGames);
10118                 break;
10119             }
10120         } else {
10121             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10122               case 1:
10123                 sprintf(cmailMsg,
10124                         _("Still need to make a move for game %s\n"),
10125                         string);
10126                 break;
10127                 
10128               case 0:
10129                 if (nCmailResults == nCmailGames) {
10130                     sprintf(cmailMsg, _("No unfinished games\n"));
10131                 } else {
10132                     sprintf(cmailMsg, _("Ready to send mail\n"));
10133                 }
10134                 break;
10135                 
10136               default:
10137                 sprintf(cmailMsg,
10138                         _("Still need to make moves for games %s\n"),
10139                         string);
10140             }
10141         }
10142     }
10143     return cmailMsg;
10144 #endif /* WIN32 */
10145 }
10146
10147 void
10148 ResetGameEvent()
10149 {
10150     if (gameMode == Training)
10151       SetTrainingModeOff();
10152
10153     Reset(TRUE, TRUE);
10154     cmailMsgLoaded = FALSE;
10155     if (appData.icsActive) {
10156       SendToICS(ics_prefix);
10157       SendToICS("refresh\n");
10158     }
10159 }
10160
10161 void
10162 ExitEvent(status)
10163      int status;
10164 {
10165     exiting++;
10166     if (exiting > 2) {
10167       /* Give up on clean exit */
10168       exit(status);
10169     }
10170     if (exiting > 1) {
10171       /* Keep trying for clean exit */
10172       return;
10173     }
10174
10175     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10176
10177     if (telnetISR != NULL) {
10178       RemoveInputSource(telnetISR);
10179     }
10180     if (icsPR != NoProc) {
10181       DestroyChildProcess(icsPR, TRUE);
10182     }
10183
10184     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10185     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10186
10187     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10188     /* make sure this other one finishes before killing it!                  */
10189     if(endingGame) { int count = 0;
10190         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10191         while(endingGame && count++ < 10) DoSleep(1);
10192         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10193     }
10194
10195     /* Kill off chess programs */
10196     if (first.pr != NoProc) {
10197         ExitAnalyzeMode();
10198         
10199         DoSleep( appData.delayBeforeQuit );
10200         SendToProgram("quit\n", &first);
10201         DoSleep( appData.delayAfterQuit );
10202         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10203     }
10204     if (second.pr != NoProc) {
10205         DoSleep( appData.delayBeforeQuit );
10206         SendToProgram("quit\n", &second);
10207         DoSleep( appData.delayAfterQuit );
10208         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10209     }
10210     if (first.isr != NULL) {
10211         RemoveInputSource(first.isr);
10212     }
10213     if (second.isr != NULL) {
10214         RemoveInputSource(second.isr);
10215     }
10216
10217     ShutDownFrontEnd();
10218     exit(status);
10219 }
10220
10221 void
10222 PauseEvent()
10223 {
10224     if (appData.debugMode)
10225         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10226     if (pausing) {
10227         pausing = FALSE;
10228         ModeHighlight();
10229         if (gameMode == MachinePlaysWhite ||
10230             gameMode == MachinePlaysBlack) {
10231             StartClocks();
10232         } else {
10233             DisplayBothClocks();
10234         }
10235         if (gameMode == PlayFromGameFile) {
10236             if (appData.timeDelay >= 0) 
10237                 AutoPlayGameLoop();
10238         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10239             Reset(FALSE, TRUE);
10240             SendToICS(ics_prefix);
10241             SendToICS("refresh\n");
10242         } else if (currentMove < forwardMostMove) {
10243             ForwardInner(forwardMostMove);
10244         }
10245         pauseExamInvalid = FALSE;
10246     } else {
10247         switch (gameMode) {
10248           default:
10249             return;
10250           case IcsExamining:
10251             pauseExamForwardMostMove = forwardMostMove;
10252             pauseExamInvalid = FALSE;
10253             /* fall through */
10254           case IcsObserving:
10255           case IcsPlayingWhite:
10256           case IcsPlayingBlack:
10257             pausing = TRUE;
10258             ModeHighlight();
10259             return;
10260           case PlayFromGameFile:
10261             (void) StopLoadGameTimer();
10262             pausing = TRUE;
10263             ModeHighlight();
10264             break;
10265           case BeginningOfGame:
10266             if (appData.icsActive) return;
10267             /* else fall through */
10268           case MachinePlaysWhite:
10269           case MachinePlaysBlack:
10270           case TwoMachinesPlay:
10271             if (forwardMostMove == 0)
10272               return;           /* don't pause if no one has moved */
10273             if ((gameMode == MachinePlaysWhite &&
10274                  !WhiteOnMove(forwardMostMove)) ||
10275                 (gameMode == MachinePlaysBlack &&
10276                  WhiteOnMove(forwardMostMove))) {
10277                 StopClocks();
10278             }
10279             pausing = TRUE;
10280             ModeHighlight();
10281             break;
10282         }
10283     }
10284 }
10285
10286 void
10287 EditCommentEvent()
10288 {
10289     char title[MSG_SIZ];
10290
10291     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10292         strcpy(title, _("Edit comment"));
10293     } else {
10294         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10295                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10296                 parseList[currentMove - 1]);
10297     }
10298
10299     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10300 }
10301
10302
10303 void
10304 EditTagsEvent()
10305 {
10306     char *tags = PGNTags(&gameInfo);
10307     EditTagsPopUp(tags);
10308     free(tags);
10309 }
10310
10311 void
10312 AnalyzeModeEvent()
10313 {
10314     if (appData.noChessProgram || gameMode == AnalyzeMode)
10315       return;
10316
10317     if (gameMode != AnalyzeFile) {
10318         if (!appData.icsEngineAnalyze) {
10319                EditGameEvent();
10320                if (gameMode != EditGame) return;
10321         }
10322         ResurrectChessProgram();
10323         SendToProgram("analyze\n", &first);
10324         first.analyzing = TRUE;
10325         /*first.maybeThinking = TRUE;*/
10326         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10327         AnalysisPopUp(_("Analysis"),
10328                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10329     }
10330     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10331     pausing = FALSE;
10332     ModeHighlight();
10333     SetGameInfo();
10334
10335     StartAnalysisClock();
10336     GetTimeMark(&lastNodeCountTime);
10337     lastNodeCount = 0;
10338 }
10339
10340 void
10341 AnalyzeFileEvent()
10342 {
10343     if (appData.noChessProgram || gameMode == AnalyzeFile)
10344       return;
10345
10346     if (gameMode != AnalyzeMode) {
10347         EditGameEvent();
10348         if (gameMode != EditGame) return;
10349         ResurrectChessProgram();
10350         SendToProgram("analyze\n", &first);
10351         first.analyzing = TRUE;
10352         /*first.maybeThinking = TRUE;*/
10353         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10354         AnalysisPopUp(_("Analysis"),
10355                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10356     }
10357     gameMode = AnalyzeFile;
10358     pausing = FALSE;
10359     ModeHighlight();
10360     SetGameInfo();
10361
10362     StartAnalysisClock();
10363     GetTimeMark(&lastNodeCountTime);
10364     lastNodeCount = 0;
10365 }
10366
10367 void
10368 MachineWhiteEvent()
10369 {
10370     char buf[MSG_SIZ];
10371     char *bookHit = NULL;
10372
10373     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10374       return;
10375
10376
10377     if (gameMode == PlayFromGameFile || 
10378         gameMode == TwoMachinesPlay  || 
10379         gameMode == Training         || 
10380         gameMode == AnalyzeMode      || 
10381         gameMode == EndOfGame)
10382         EditGameEvent();
10383
10384     if (gameMode == EditPosition) 
10385         EditPositionDone();
10386
10387     if (!WhiteOnMove(currentMove)) {
10388         DisplayError(_("It is not White's turn"), 0);
10389         return;
10390     }
10391   
10392     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10393       ExitAnalyzeMode();
10394
10395     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10396         gameMode == AnalyzeFile)
10397         TruncateGame();
10398
10399     ResurrectChessProgram();    /* in case it isn't running */
10400     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10401         gameMode = MachinePlaysWhite;
10402         ResetClocks();
10403     } else
10404     gameMode = MachinePlaysWhite;
10405     pausing = FALSE;
10406     ModeHighlight();
10407     SetGameInfo();
10408     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10409     DisplayTitle(buf);
10410     if (first.sendName) {
10411       sprintf(buf, "name %s\n", gameInfo.black);
10412       SendToProgram(buf, &first);
10413     }
10414     if (first.sendTime) {
10415       if (first.useColors) {
10416         SendToProgram("black\n", &first); /*gnu kludge*/
10417       }
10418       SendTimeRemaining(&first, TRUE);
10419     }
10420     if (first.useColors) {
10421       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10422     }
10423     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10424     SetMachineThinkingEnables();
10425     first.maybeThinking = TRUE;
10426     StartClocks();
10427     firstMove = FALSE;
10428
10429     if (appData.autoFlipView && !flipView) {
10430       flipView = !flipView;
10431       DrawPosition(FALSE, NULL);
10432       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10433     }
10434
10435     if(bookHit) { // [HGM] book: simulate book reply
10436         static char bookMove[MSG_SIZ]; // a bit generous?
10437
10438         programStats.nodes = programStats.depth = programStats.time = 
10439         programStats.score = programStats.got_only_move = 0;
10440         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10441
10442         strcpy(bookMove, "move ");
10443         strcat(bookMove, bookHit);
10444         HandleMachineMove(bookMove, &first);
10445     }
10446 }
10447
10448 void
10449 MachineBlackEvent()
10450 {
10451     char buf[MSG_SIZ];
10452    char *bookHit = NULL;
10453
10454     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10455         return;
10456
10457
10458     if (gameMode == PlayFromGameFile || 
10459         gameMode == TwoMachinesPlay  || 
10460         gameMode == Training         || 
10461         gameMode == AnalyzeMode      || 
10462         gameMode == EndOfGame)
10463         EditGameEvent();
10464
10465     if (gameMode == EditPosition) 
10466         EditPositionDone();
10467
10468     if (WhiteOnMove(currentMove)) {
10469         DisplayError(_("It is not Black's turn"), 0);
10470         return;
10471     }
10472     
10473     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10474       ExitAnalyzeMode();
10475
10476     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10477         gameMode == AnalyzeFile)
10478         TruncateGame();
10479
10480     ResurrectChessProgram();    /* in case it isn't running */
10481     gameMode = MachinePlaysBlack;
10482     pausing = FALSE;
10483     ModeHighlight();
10484     SetGameInfo();
10485     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10486     DisplayTitle(buf);
10487     if (first.sendName) {
10488       sprintf(buf, "name %s\n", gameInfo.white);
10489       SendToProgram(buf, &first);
10490     }
10491     if (first.sendTime) {
10492       if (first.useColors) {
10493         SendToProgram("white\n", &first); /*gnu kludge*/
10494       }
10495       SendTimeRemaining(&first, FALSE);
10496     }
10497     if (first.useColors) {
10498       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10499     }
10500     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10501     SetMachineThinkingEnables();
10502     first.maybeThinking = TRUE;
10503     StartClocks();
10504
10505     if (appData.autoFlipView && flipView) {
10506       flipView = !flipView;
10507       DrawPosition(FALSE, NULL);
10508       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10509     }
10510     if(bookHit) { // [HGM] book: simulate book reply
10511         static char bookMove[MSG_SIZ]; // a bit generous?
10512
10513         programStats.nodes = programStats.depth = programStats.time = 
10514         programStats.score = programStats.got_only_move = 0;
10515         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10516
10517         strcpy(bookMove, "move ");
10518         strcat(bookMove, bookHit);
10519         HandleMachineMove(bookMove, &first);
10520     }
10521 }
10522
10523
10524 void
10525 DisplayTwoMachinesTitle()
10526 {
10527     char buf[MSG_SIZ];
10528     if (appData.matchGames > 0) {
10529         if (first.twoMachinesColor[0] == 'w') {
10530             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10531                     gameInfo.white, gameInfo.black,
10532                     first.matchWins, second.matchWins,
10533                     matchGame - 1 - (first.matchWins + second.matchWins));
10534         } else {
10535             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10536                     gameInfo.white, gameInfo.black,
10537                     second.matchWins, first.matchWins,
10538                     matchGame - 1 - (first.matchWins + second.matchWins));
10539         }
10540     } else {
10541         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10542     }
10543     DisplayTitle(buf);
10544 }
10545
10546 void
10547 TwoMachinesEvent P((void))
10548 {
10549     int i;
10550     char buf[MSG_SIZ];
10551     ChessProgramState *onmove;
10552     char *bookHit = NULL;
10553     
10554     if (appData.noChessProgram) return;
10555
10556     switch (gameMode) {
10557       case TwoMachinesPlay:
10558         return;
10559       case MachinePlaysWhite:
10560       case MachinePlaysBlack:
10561         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10562             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10563             return;
10564         }
10565         /* fall through */
10566       case BeginningOfGame:
10567       case PlayFromGameFile:
10568       case EndOfGame:
10569         EditGameEvent();
10570         if (gameMode != EditGame) return;
10571         break;
10572       case EditPosition:
10573         EditPositionDone();
10574         break;
10575       case AnalyzeMode:
10576       case AnalyzeFile:
10577         ExitAnalyzeMode();
10578         break;
10579       case EditGame:
10580       default:
10581         break;
10582     }
10583
10584     forwardMostMove = currentMove;
10585     ResurrectChessProgram();    /* in case first program isn't running */
10586
10587     if (second.pr == NULL) {
10588         StartChessProgram(&second);
10589         if (second.protocolVersion == 1) {
10590           TwoMachinesEventIfReady();
10591         } else {
10592           /* kludge: allow timeout for initial "feature" command */
10593           FreezeUI();
10594           DisplayMessage("", _("Starting second chess program"));
10595           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10596         }
10597         return;
10598     }
10599     DisplayMessage("", "");
10600     InitChessProgram(&second, FALSE);
10601     SendToProgram("force\n", &second);
10602     if (startedFromSetupPosition) {
10603         SendBoard(&second, backwardMostMove);
10604     if (appData.debugMode) {
10605         fprintf(debugFP, "Two Machines\n");
10606     }
10607     }
10608     for (i = backwardMostMove; i < forwardMostMove; i++) {
10609         SendMoveToProgram(i, &second);
10610     }
10611
10612     gameMode = TwoMachinesPlay;
10613     pausing = FALSE;
10614     ModeHighlight();
10615     SetGameInfo();
10616     DisplayTwoMachinesTitle();
10617     firstMove = TRUE;
10618     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10619         onmove = &first;
10620     } else {
10621         onmove = &second;
10622     }
10623
10624     SendToProgram(first.computerString, &first);
10625     if (first.sendName) {
10626       sprintf(buf, "name %s\n", second.tidy);
10627       SendToProgram(buf, &first);
10628     }
10629     SendToProgram(second.computerString, &second);
10630     if (second.sendName) {
10631       sprintf(buf, "name %s\n", first.tidy);
10632       SendToProgram(buf, &second);
10633     }
10634
10635     ResetClocks();
10636     if (!first.sendTime || !second.sendTime) {
10637         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10638         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10639     }
10640     if (onmove->sendTime) {
10641       if (onmove->useColors) {
10642         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10643       }
10644       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10645     }
10646     if (onmove->useColors) {
10647       SendToProgram(onmove->twoMachinesColor, onmove);
10648     }
10649     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10650 //    SendToProgram("go\n", onmove);
10651     onmove->maybeThinking = TRUE;
10652     SetMachineThinkingEnables();
10653
10654     StartClocks();
10655
10656     if(bookHit) { // [HGM] book: simulate book reply
10657         static char bookMove[MSG_SIZ]; // a bit generous?
10658
10659         programStats.nodes = programStats.depth = programStats.time = 
10660         programStats.score = programStats.got_only_move = 0;
10661         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10662
10663         strcpy(bookMove, "move ");
10664         strcat(bookMove, bookHit);
10665         HandleMachineMove(bookMove, &first);
10666     }
10667 }
10668
10669 void
10670 TrainingEvent()
10671 {
10672     if (gameMode == Training) {
10673       SetTrainingModeOff();
10674       gameMode = PlayFromGameFile;
10675       DisplayMessage("", _("Training mode off"));
10676     } else {
10677       gameMode = Training;
10678       animateTraining = appData.animate;
10679
10680       /* make sure we are not already at the end of the game */
10681       if (currentMove < forwardMostMove) {
10682         SetTrainingModeOn();
10683         DisplayMessage("", _("Training mode on"));
10684       } else {
10685         gameMode = PlayFromGameFile;
10686         DisplayError(_("Already at end of game"), 0);
10687       }
10688     }
10689     ModeHighlight();
10690 }
10691
10692 void
10693 IcsClientEvent()
10694 {
10695     if (!appData.icsActive) return;
10696     switch (gameMode) {
10697       case IcsPlayingWhite:
10698       case IcsPlayingBlack:
10699       case IcsObserving:
10700       case IcsIdle:
10701       case BeginningOfGame:
10702       case IcsExamining:
10703         return;
10704
10705       case EditGame:
10706         break;
10707
10708       case EditPosition:
10709         EditPositionDone();
10710         break;
10711
10712       case AnalyzeMode:
10713       case AnalyzeFile:
10714         ExitAnalyzeMode();
10715         break;
10716         
10717       default:
10718         EditGameEvent();
10719         break;
10720     }
10721
10722     gameMode = IcsIdle;
10723     ModeHighlight();
10724     return;
10725 }
10726
10727
10728 void
10729 EditGameEvent()
10730 {
10731     int i;
10732
10733     switch (gameMode) {
10734       case Training:
10735         SetTrainingModeOff();
10736         break;
10737       case MachinePlaysWhite:
10738       case MachinePlaysBlack:
10739       case BeginningOfGame:
10740         SendToProgram("force\n", &first);
10741         SetUserThinkingEnables();
10742         break;
10743       case PlayFromGameFile:
10744         (void) StopLoadGameTimer();
10745         if (gameFileFP != NULL) {
10746             gameFileFP = NULL;
10747         }
10748         break;
10749       case EditPosition:
10750         EditPositionDone();
10751         break;
10752       case AnalyzeMode:
10753       case AnalyzeFile:
10754         ExitAnalyzeMode();
10755         SendToProgram("force\n", &first);
10756         break;
10757       case TwoMachinesPlay:
10758         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10759         ResurrectChessProgram();
10760         SetUserThinkingEnables();
10761         break;
10762       case EndOfGame:
10763         ResurrectChessProgram();
10764         break;
10765       case IcsPlayingBlack:
10766       case IcsPlayingWhite:
10767         DisplayError(_("Warning: You are still playing a game"), 0);
10768         break;
10769       case IcsObserving:
10770         DisplayError(_("Warning: You are still observing a game"), 0);
10771         break;
10772       case IcsExamining:
10773         DisplayError(_("Warning: You are still examining a game"), 0);
10774         break;
10775       case IcsIdle:
10776         break;
10777       case EditGame:
10778       default:
10779         return;
10780     }
10781     
10782     pausing = FALSE;
10783     StopClocks();
10784     first.offeredDraw = second.offeredDraw = 0;
10785
10786     if (gameMode == PlayFromGameFile) {
10787         whiteTimeRemaining = timeRemaining[0][currentMove];
10788         blackTimeRemaining = timeRemaining[1][currentMove];
10789         DisplayTitle("");
10790     }
10791
10792     if (gameMode == MachinePlaysWhite ||
10793         gameMode == MachinePlaysBlack ||
10794         gameMode == TwoMachinesPlay ||
10795         gameMode == EndOfGame) {
10796         i = forwardMostMove;
10797         while (i > currentMove) {
10798             SendToProgram("undo\n", &first);
10799             i--;
10800         }
10801         whiteTimeRemaining = timeRemaining[0][currentMove];
10802         blackTimeRemaining = timeRemaining[1][currentMove];
10803         DisplayBothClocks();
10804         if (whiteFlag || blackFlag) {
10805             whiteFlag = blackFlag = 0;
10806         }
10807         DisplayTitle("");
10808     }           
10809     
10810     gameMode = EditGame;
10811     ModeHighlight();
10812     SetGameInfo();
10813 }
10814
10815
10816 void
10817 EditPositionEvent()
10818 {
10819     if (gameMode == EditPosition) {
10820         EditGameEvent();
10821         return;
10822     }
10823     
10824     EditGameEvent();
10825     if (gameMode != EditGame) return;
10826     
10827     gameMode = EditPosition;
10828     ModeHighlight();
10829     SetGameInfo();
10830     if (currentMove > 0)
10831       CopyBoard(boards[0], boards[currentMove]);
10832     
10833     blackPlaysFirst = !WhiteOnMove(currentMove);
10834     ResetClocks();
10835     currentMove = forwardMostMove = backwardMostMove = 0;
10836     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10837     DisplayMove(-1);
10838 }
10839
10840 void
10841 ExitAnalyzeMode()
10842 {
10843     /* [DM] icsEngineAnalyze - possible call from other functions */
10844     if (appData.icsEngineAnalyze) {
10845         appData.icsEngineAnalyze = FALSE;
10846
10847         DisplayMessage("",_("Close ICS engine analyze..."));
10848     }
10849     if (first.analysisSupport && first.analyzing) {
10850       SendToProgram("exit\n", &first);
10851       first.analyzing = FALSE;
10852     }
10853     AnalysisPopDown();
10854     thinkOutput[0] = NULLCHAR;
10855 }
10856
10857 void
10858 EditPositionDone()
10859 {
10860     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10861
10862     startedFromSetupPosition = TRUE;
10863     InitChessProgram(&first, FALSE);
10864     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10865     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10866         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10867         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10868     } else castlingRights[0][2] = -1;
10869     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10870         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10871         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10872     } else castlingRights[0][5] = -1;
10873     SendToProgram("force\n", &first);
10874     if (blackPlaysFirst) {
10875         strcpy(moveList[0], "");
10876         strcpy(parseList[0], "");
10877         currentMove = forwardMostMove = backwardMostMove = 1;
10878         CopyBoard(boards[1], boards[0]);
10879         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10880         { int i;
10881           epStatus[1] = epStatus[0];
10882           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10883         }
10884     } else {
10885         currentMove = forwardMostMove = backwardMostMove = 0;
10886     }
10887     SendBoard(&first, forwardMostMove);
10888     if (appData.debugMode) {
10889         fprintf(debugFP, "EditPosDone\n");
10890     }
10891     DisplayTitle("");
10892     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10893     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10894     gameMode = EditGame;
10895     ModeHighlight();
10896     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10897     ClearHighlights(); /* [AS] */
10898 }
10899
10900 /* Pause for `ms' milliseconds */
10901 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10902 void
10903 TimeDelay(ms)
10904      long ms;
10905 {
10906     TimeMark m1, m2;
10907
10908     GetTimeMark(&m1);
10909     do {
10910         GetTimeMark(&m2);
10911     } while (SubtractTimeMarks(&m2, &m1) < ms);
10912 }
10913
10914 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10915 void
10916 SendMultiLineToICS(buf)
10917      char *buf;
10918 {
10919     char temp[MSG_SIZ+1], *p;
10920     int len;
10921
10922     len = strlen(buf);
10923     if (len > MSG_SIZ)
10924       len = MSG_SIZ;
10925   
10926     strncpy(temp, buf, len);
10927     temp[len] = 0;
10928
10929     p = temp;
10930     while (*p) {
10931         if (*p == '\n' || *p == '\r')
10932           *p = ' ';
10933         ++p;
10934     }
10935
10936     strcat(temp, "\n");
10937     SendToICS(temp);
10938     SendToPlayer(temp, strlen(temp));
10939 }
10940
10941 void
10942 SetWhiteToPlayEvent()
10943 {
10944     if (gameMode == EditPosition) {
10945         blackPlaysFirst = FALSE;
10946         DisplayBothClocks();    /* works because currentMove is 0 */
10947     } else if (gameMode == IcsExamining) {
10948         SendToICS(ics_prefix);
10949         SendToICS("tomove white\n");
10950     }
10951 }
10952
10953 void
10954 SetBlackToPlayEvent()
10955 {
10956     if (gameMode == EditPosition) {
10957         blackPlaysFirst = TRUE;
10958         currentMove = 1;        /* kludge */
10959         DisplayBothClocks();
10960         currentMove = 0;
10961     } else if (gameMode == IcsExamining) {
10962         SendToICS(ics_prefix);
10963         SendToICS("tomove black\n");
10964     }
10965 }
10966
10967 void
10968 EditPositionMenuEvent(selection, x, y)
10969      ChessSquare selection;
10970      int x, y;
10971 {
10972     char buf[MSG_SIZ];
10973     ChessSquare piece = boards[0][y][x];
10974
10975     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10976
10977     switch (selection) {
10978       case ClearBoard:
10979         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10980             SendToICS(ics_prefix);
10981             SendToICS("bsetup clear\n");
10982         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10983             SendToICS(ics_prefix);
10984             SendToICS("clearboard\n");
10985         } else {
10986             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10987                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10988                 for (y = 0; y < BOARD_HEIGHT; y++) {
10989                     if (gameMode == IcsExamining) {
10990                         if (boards[currentMove][y][x] != EmptySquare) {
10991                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
10992                                     AAA + x, ONE + y);
10993                             SendToICS(buf);
10994                         }
10995                     } else {
10996                         boards[0][y][x] = p;
10997                     }
10998                 }
10999             }
11000         }
11001         if (gameMode == EditPosition) {
11002             DrawPosition(FALSE, boards[0]);
11003         }
11004         break;
11005
11006       case WhitePlay:
11007         SetWhiteToPlayEvent();
11008         break;
11009
11010       case BlackPlay:
11011         SetBlackToPlayEvent();
11012         break;
11013
11014       case EmptySquare:
11015         if (gameMode == IcsExamining) {
11016             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11017             SendToICS(buf);
11018         } else {
11019             boards[0][y][x] = EmptySquare;
11020             DrawPosition(FALSE, boards[0]);
11021         }
11022         break;
11023
11024       case PromotePiece:
11025         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11026            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11027             selection = (ChessSquare) (PROMOTED piece);
11028         } else if(piece == EmptySquare) selection = WhiteSilver;
11029         else selection = (ChessSquare)((int)piece - 1);
11030         goto defaultlabel;
11031
11032       case DemotePiece:
11033         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11034            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11035             selection = (ChessSquare) (DEMOTED piece);
11036         } else if(piece == EmptySquare) selection = BlackSilver;
11037         else selection = (ChessSquare)((int)piece + 1);       
11038         goto defaultlabel;
11039
11040       case WhiteQueen:
11041       case BlackQueen:
11042         if(gameInfo.variant == VariantShatranj ||
11043            gameInfo.variant == VariantXiangqi  ||
11044            gameInfo.variant == VariantCourier    )
11045             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11046         goto defaultlabel;
11047
11048       case WhiteKing:
11049       case BlackKing:
11050         if(gameInfo.variant == VariantXiangqi)
11051             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11052         if(gameInfo.variant == VariantKnightmate)
11053             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11054       default:
11055         defaultlabel:
11056         if (gameMode == IcsExamining) {
11057             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11058                     PieceToChar(selection), AAA + x, ONE + y);
11059             SendToICS(buf);
11060         } else {
11061             boards[0][y][x] = selection;
11062             DrawPosition(FALSE, boards[0]);
11063         }
11064         break;
11065     }
11066 }
11067
11068
11069 void
11070 DropMenuEvent(selection, x, y)
11071      ChessSquare selection;
11072      int x, y;
11073 {
11074     ChessMove moveType;
11075
11076     switch (gameMode) {
11077       case IcsPlayingWhite:
11078       case MachinePlaysBlack:
11079         if (!WhiteOnMove(currentMove)) {
11080             DisplayMoveError(_("It is Black's turn"));
11081             return;
11082         }
11083         moveType = WhiteDrop;
11084         break;
11085       case IcsPlayingBlack:
11086       case MachinePlaysWhite:
11087         if (WhiteOnMove(currentMove)) {
11088             DisplayMoveError(_("It is White's turn"));
11089             return;
11090         }
11091         moveType = BlackDrop;
11092         break;
11093       case EditGame:
11094         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11095         break;
11096       default:
11097         return;
11098     }
11099
11100     if (moveType == BlackDrop && selection < BlackPawn) {
11101       selection = (ChessSquare) ((int) selection
11102                                  + (int) BlackPawn - (int) WhitePawn);
11103     }
11104     if (boards[currentMove][y][x] != EmptySquare) {
11105         DisplayMoveError(_("That square is occupied"));
11106         return;
11107     }
11108
11109     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11110 }
11111
11112 void
11113 AcceptEvent()
11114 {
11115     /* Accept a pending offer of any kind from opponent */
11116     
11117     if (appData.icsActive) {
11118         SendToICS(ics_prefix);
11119         SendToICS("accept\n");
11120     } else if (cmailMsgLoaded) {
11121         if (currentMove == cmailOldMove &&
11122             commentList[cmailOldMove] != NULL &&
11123             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11124                    "Black offers a draw" : "White offers a draw")) {
11125             TruncateGame();
11126             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11127             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11128         } else {
11129             DisplayError(_("There is no pending offer on this move"), 0);
11130             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11131         }
11132     } else {
11133         /* Not used for offers from chess program */
11134     }
11135 }
11136
11137 void
11138 DeclineEvent()
11139 {
11140     /* Decline a pending offer of any kind from opponent */
11141     
11142     if (appData.icsActive) {
11143         SendToICS(ics_prefix);
11144         SendToICS("decline\n");
11145     } else if (cmailMsgLoaded) {
11146         if (currentMove == cmailOldMove &&
11147             commentList[cmailOldMove] != NULL &&
11148             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11149                    "Black offers a draw" : "White offers a draw")) {
11150 #ifdef NOTDEF
11151             AppendComment(cmailOldMove, "Draw declined");
11152             DisplayComment(cmailOldMove - 1, "Draw declined");
11153 #endif /*NOTDEF*/
11154         } else {
11155             DisplayError(_("There is no pending offer on this move"), 0);
11156         }
11157     } else {
11158         /* Not used for offers from chess program */
11159     }
11160 }
11161
11162 void
11163 RematchEvent()
11164 {
11165     /* Issue ICS rematch command */
11166     if (appData.icsActive) {
11167         SendToICS(ics_prefix);
11168         SendToICS("rematch\n");
11169     }
11170 }
11171
11172 void
11173 CallFlagEvent()
11174 {
11175     /* Call your opponent's flag (claim a win on time) */
11176     if (appData.icsActive) {
11177         SendToICS(ics_prefix);
11178         SendToICS("flag\n");
11179     } else {
11180         switch (gameMode) {
11181           default:
11182             return;
11183           case MachinePlaysWhite:
11184             if (whiteFlag) {
11185                 if (blackFlag)
11186                   GameEnds(GameIsDrawn, "Both players ran out of time",
11187                            GE_PLAYER);
11188                 else
11189                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11190             } else {
11191                 DisplayError(_("Your opponent is not out of time"), 0);
11192             }
11193             break;
11194           case MachinePlaysBlack:
11195             if (blackFlag) {
11196                 if (whiteFlag)
11197                   GameEnds(GameIsDrawn, "Both players ran out of time",
11198                            GE_PLAYER);
11199                 else
11200                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11201             } else {
11202                 DisplayError(_("Your opponent is not out of time"), 0);
11203             }
11204             break;
11205         }
11206     }
11207 }
11208
11209 void
11210 DrawEvent()
11211 {
11212     /* Offer draw or accept pending draw offer from opponent */
11213     
11214     if (appData.icsActive) {
11215         /* Note: tournament rules require draw offers to be
11216            made after you make your move but before you punch
11217            your clock.  Currently ICS doesn't let you do that;
11218            instead, you immediately punch your clock after making
11219            a move, but you can offer a draw at any time. */
11220         
11221         SendToICS(ics_prefix);
11222         SendToICS("draw\n");
11223     } else if (cmailMsgLoaded) {
11224         if (currentMove == cmailOldMove &&
11225             commentList[cmailOldMove] != NULL &&
11226             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11227                    "Black offers a draw" : "White offers a draw")) {
11228             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11229             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11230         } else if (currentMove == cmailOldMove + 1) {
11231             char *offer = WhiteOnMove(cmailOldMove) ?
11232               "White offers a draw" : "Black offers a draw";
11233             AppendComment(currentMove, offer);
11234             DisplayComment(currentMove - 1, offer);
11235             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11236         } else {
11237             DisplayError(_("You must make your move before offering a draw"), 0);
11238             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11239         }
11240     } else if (first.offeredDraw) {
11241         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11242     } else {
11243         if (first.sendDrawOffers) {
11244             SendToProgram("draw\n", &first);
11245             userOfferedDraw = TRUE;
11246         }
11247     }
11248 }
11249
11250 void
11251 AdjournEvent()
11252 {
11253     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11254     
11255     if (appData.icsActive) {
11256         SendToICS(ics_prefix);
11257         SendToICS("adjourn\n");
11258     } else {
11259         /* Currently GNU Chess doesn't offer or accept Adjourns */
11260     }
11261 }
11262
11263
11264 void
11265 AbortEvent()
11266 {
11267     /* Offer Abort or accept pending Abort offer from opponent */
11268     
11269     if (appData.icsActive) {
11270         SendToICS(ics_prefix);
11271         SendToICS("abort\n");
11272     } else {
11273         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11274     }
11275 }
11276
11277 void
11278 ResignEvent()
11279 {
11280     /* Resign.  You can do this even if it's not your turn. */
11281     
11282     if (appData.icsActive) {
11283         SendToICS(ics_prefix);
11284         SendToICS("resign\n");
11285     } else {
11286         switch (gameMode) {
11287           case MachinePlaysWhite:
11288             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11289             break;
11290           case MachinePlaysBlack:
11291             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11292             break;
11293           case EditGame:
11294             if (cmailMsgLoaded) {
11295                 TruncateGame();
11296                 if (WhiteOnMove(cmailOldMove)) {
11297                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11298                 } else {
11299                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11300                 }
11301                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11302             }
11303             break;
11304           default:
11305             break;
11306         }
11307     }
11308 }
11309
11310
11311 void
11312 StopObservingEvent()
11313 {
11314     /* Stop observing current games */
11315     SendToICS(ics_prefix);
11316     SendToICS("unobserve\n");
11317 }
11318
11319 void
11320 StopExaminingEvent()
11321 {
11322     /* Stop observing current game */
11323     SendToICS(ics_prefix);
11324     SendToICS("unexamine\n");
11325 }
11326
11327 void
11328 ForwardInner(target)
11329      int target;
11330 {
11331     int limit;
11332
11333     if (appData.debugMode)
11334         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11335                 target, currentMove, forwardMostMove);
11336
11337     if (gameMode == EditPosition)
11338       return;
11339
11340     if (gameMode == PlayFromGameFile && !pausing)
11341       PauseEvent();
11342     
11343     if (gameMode == IcsExamining && pausing)
11344       limit = pauseExamForwardMostMove;
11345     else
11346       limit = forwardMostMove;
11347     
11348     if (target > limit) target = limit;
11349
11350     if (target > 0 && moveList[target - 1][0]) {
11351         int fromX, fromY, toX, toY;
11352         toX = moveList[target - 1][2] - AAA;
11353         toY = moveList[target - 1][3] - ONE;
11354         if (moveList[target - 1][1] == '@') {
11355             if (appData.highlightLastMove) {
11356                 SetHighlights(-1, -1, toX, toY);
11357             }
11358         } else {
11359             fromX = moveList[target - 1][0] - AAA;
11360             fromY = moveList[target - 1][1] - ONE;
11361             if (target == currentMove + 1) {
11362                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11363             }
11364             if (appData.highlightLastMove) {
11365                 SetHighlights(fromX, fromY, toX, toY);
11366             }
11367         }
11368     }
11369     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11370         gameMode == Training || gameMode == PlayFromGameFile || 
11371         gameMode == AnalyzeFile) {
11372         while (currentMove < target) {
11373             SendMoveToProgram(currentMove++, &first);
11374         }
11375     } else {
11376         currentMove = target;
11377     }
11378     
11379     if (gameMode == EditGame || gameMode == EndOfGame) {
11380         whiteTimeRemaining = timeRemaining[0][currentMove];
11381         blackTimeRemaining = timeRemaining[1][currentMove];
11382     }
11383     DisplayBothClocks();
11384     DisplayMove(currentMove - 1);
11385     DrawPosition(FALSE, boards[currentMove]);
11386     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11387     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11388         DisplayComment(currentMove - 1, commentList[currentMove]);
11389     }
11390 }
11391
11392
11393 void
11394 ForwardEvent()
11395 {
11396     if (gameMode == IcsExamining && !pausing) {
11397         SendToICS(ics_prefix);
11398         SendToICS("forward\n");
11399     } else {
11400         ForwardInner(currentMove + 1);
11401     }
11402 }
11403
11404 void
11405 ToEndEvent()
11406 {
11407     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11408         /* to optimze, we temporarily turn off analysis mode while we feed
11409          * the remaining moves to the engine. Otherwise we get analysis output
11410          * after each move.
11411          */ 
11412         if (first.analysisSupport) {
11413           SendToProgram("exit\nforce\n", &first);
11414           first.analyzing = FALSE;
11415         }
11416     }
11417         
11418     if (gameMode == IcsExamining && !pausing) {
11419         SendToICS(ics_prefix);
11420         SendToICS("forward 999999\n");
11421     } else {
11422         ForwardInner(forwardMostMove);
11423     }
11424
11425     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11426         /* we have fed all the moves, so reactivate analysis mode */
11427         SendToProgram("analyze\n", &first);
11428         first.analyzing = TRUE;
11429         /*first.maybeThinking = TRUE;*/
11430         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11431     }
11432 }
11433
11434 void
11435 BackwardInner(target)
11436      int target;
11437 {
11438     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11439
11440     if (appData.debugMode)
11441         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11442                 target, currentMove, forwardMostMove);
11443
11444     if (gameMode == EditPosition) return;
11445     if (currentMove <= backwardMostMove) {
11446         ClearHighlights();
11447         DrawPosition(full_redraw, boards[currentMove]);
11448         return;
11449     }
11450     if (gameMode == PlayFromGameFile && !pausing)
11451       PauseEvent();
11452     
11453     if (moveList[target][0]) {
11454         int fromX, fromY, toX, toY;
11455         toX = moveList[target][2] - AAA;
11456         toY = moveList[target][3] - ONE;
11457         if (moveList[target][1] == '@') {
11458             if (appData.highlightLastMove) {
11459                 SetHighlights(-1, -1, toX, toY);
11460             }
11461         } else {
11462             fromX = moveList[target][0] - AAA;
11463             fromY = moveList[target][1] - ONE;
11464             if (target == currentMove - 1) {
11465                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11466             }
11467             if (appData.highlightLastMove) {
11468                 SetHighlights(fromX, fromY, toX, toY);
11469             }
11470         }
11471     }
11472     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11473         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11474         while (currentMove > target) {
11475             SendToProgram("undo\n", &first);
11476             currentMove--;
11477         }
11478     } else {
11479         currentMove = target;
11480     }
11481     
11482     if (gameMode == EditGame || gameMode == EndOfGame) {
11483         whiteTimeRemaining = timeRemaining[0][currentMove];
11484         blackTimeRemaining = timeRemaining[1][currentMove];
11485     }
11486     DisplayBothClocks();
11487     DisplayMove(currentMove - 1);
11488     DrawPosition(full_redraw, boards[currentMove]);
11489     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11490     // [HGM] PV info: routine tests if comment empty
11491     DisplayComment(currentMove - 1, commentList[currentMove]);
11492 }
11493
11494 void
11495 BackwardEvent()
11496 {
11497     if (gameMode == IcsExamining && !pausing) {
11498         SendToICS(ics_prefix);
11499         SendToICS("backward\n");
11500     } else {
11501         BackwardInner(currentMove - 1);
11502     }
11503 }
11504
11505 void
11506 ToStartEvent()
11507 {
11508     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11509         /* to optimze, we temporarily turn off analysis mode while we undo
11510          * all the moves. Otherwise we get analysis output after each undo.
11511          */ 
11512         if (first.analysisSupport) {
11513           SendToProgram("exit\nforce\n", &first);
11514           first.analyzing = FALSE;
11515         }
11516     }
11517
11518     if (gameMode == IcsExamining && !pausing) {
11519         SendToICS(ics_prefix);
11520         SendToICS("backward 999999\n");
11521     } else {
11522         BackwardInner(backwardMostMove);
11523     }
11524
11525     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11526         /* we have fed all the moves, so reactivate analysis mode */
11527         SendToProgram("analyze\n", &first);
11528         first.analyzing = TRUE;
11529         /*first.maybeThinking = TRUE;*/
11530         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11531     }
11532 }
11533
11534 void
11535 ToNrEvent(int to)
11536 {
11537   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11538   if (to >= forwardMostMove) to = forwardMostMove;
11539   if (to <= backwardMostMove) to = backwardMostMove;
11540   if (to < currentMove) {
11541     BackwardInner(to);
11542   } else {
11543     ForwardInner(to);
11544   }
11545 }
11546
11547 void
11548 RevertEvent()
11549 {
11550     if (gameMode != IcsExamining) {
11551         DisplayError(_("You are not examining a game"), 0);
11552         return;
11553     }
11554     if (pausing) {
11555         DisplayError(_("You can't revert while pausing"), 0);
11556         return;
11557     }
11558     SendToICS(ics_prefix);
11559     SendToICS("revert\n");
11560 }
11561
11562 void
11563 RetractMoveEvent()
11564 {
11565     switch (gameMode) {
11566       case MachinePlaysWhite:
11567       case MachinePlaysBlack:
11568         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11569             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11570             return;
11571         }
11572         if (forwardMostMove < 2) return;
11573         currentMove = forwardMostMove = forwardMostMove - 2;
11574         whiteTimeRemaining = timeRemaining[0][currentMove];
11575         blackTimeRemaining = timeRemaining[1][currentMove];
11576         DisplayBothClocks();
11577         DisplayMove(currentMove - 1);
11578         ClearHighlights();/*!! could figure this out*/
11579         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11580         SendToProgram("remove\n", &first);
11581         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11582         break;
11583
11584       case BeginningOfGame:
11585       default:
11586         break;
11587
11588       case IcsPlayingWhite:
11589       case IcsPlayingBlack:
11590         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11591             SendToICS(ics_prefix);
11592             SendToICS("takeback 2\n");
11593         } else {
11594             SendToICS(ics_prefix);
11595             SendToICS("takeback 1\n");
11596         }
11597         break;
11598     }
11599 }
11600
11601 void
11602 MoveNowEvent()
11603 {
11604     ChessProgramState *cps;
11605
11606     switch (gameMode) {
11607       case MachinePlaysWhite:
11608         if (!WhiteOnMove(forwardMostMove)) {
11609             DisplayError(_("It is your turn"), 0);
11610             return;
11611         }
11612         cps = &first;
11613         break;
11614       case MachinePlaysBlack:
11615         if (WhiteOnMove(forwardMostMove)) {
11616             DisplayError(_("It is your turn"), 0);
11617             return;
11618         }
11619         cps = &first;
11620         break;
11621       case TwoMachinesPlay:
11622         if (WhiteOnMove(forwardMostMove) ==
11623             (first.twoMachinesColor[0] == 'w')) {
11624             cps = &first;
11625         } else {
11626             cps = &second;
11627         }
11628         break;
11629       case BeginningOfGame:
11630       default:
11631         return;
11632     }
11633     SendToProgram("?\n", cps);
11634 }
11635
11636 void
11637 TruncateGameEvent()
11638 {
11639     EditGameEvent();
11640     if (gameMode != EditGame) return;
11641     TruncateGame();
11642 }
11643
11644 void
11645 TruncateGame()
11646 {
11647     if (forwardMostMove > currentMove) {
11648         if (gameInfo.resultDetails != NULL) {
11649             free(gameInfo.resultDetails);
11650             gameInfo.resultDetails = NULL;
11651             gameInfo.result = GameUnfinished;
11652         }
11653         forwardMostMove = currentMove;
11654         HistorySet(parseList, backwardMostMove, forwardMostMove,
11655                    currentMove-1);
11656     }
11657 }
11658
11659 void
11660 HintEvent()
11661 {
11662     if (appData.noChessProgram) return;
11663     switch (gameMode) {
11664       case MachinePlaysWhite:
11665         if (WhiteOnMove(forwardMostMove)) {
11666             DisplayError(_("Wait until your turn"), 0);
11667             return;
11668         }
11669         break;
11670       case BeginningOfGame:
11671       case MachinePlaysBlack:
11672         if (!WhiteOnMove(forwardMostMove)) {
11673             DisplayError(_("Wait until your turn"), 0);
11674             return;
11675         }
11676         break;
11677       default:
11678         DisplayError(_("No hint available"), 0);
11679         return;
11680     }
11681     SendToProgram("hint\n", &first);
11682     hintRequested = TRUE;
11683 }
11684
11685 void
11686 BookEvent()
11687 {
11688     if (appData.noChessProgram) return;
11689     switch (gameMode) {
11690       case MachinePlaysWhite:
11691         if (WhiteOnMove(forwardMostMove)) {
11692             DisplayError(_("Wait until your turn"), 0);
11693             return;
11694         }
11695         break;
11696       case BeginningOfGame:
11697       case MachinePlaysBlack:
11698         if (!WhiteOnMove(forwardMostMove)) {
11699             DisplayError(_("Wait until your turn"), 0);
11700             return;
11701         }
11702         break;
11703       case EditPosition:
11704         EditPositionDone();
11705         break;
11706       case TwoMachinesPlay:
11707         return;
11708       default:
11709         break;
11710     }
11711     SendToProgram("bk\n", &first);
11712     bookOutput[0] = NULLCHAR;
11713     bookRequested = TRUE;
11714 }
11715
11716 void
11717 AboutGameEvent()
11718 {
11719     char *tags = PGNTags(&gameInfo);
11720     TagsPopUp(tags, CmailMsg());
11721     free(tags);
11722 }
11723
11724 /* end button procedures */
11725
11726 void
11727 PrintPosition(fp, move)
11728      FILE *fp;
11729      int move;
11730 {
11731     int i, j;
11732     
11733     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11734         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11735             char c = PieceToChar(boards[move][i][j]);
11736             fputc(c == 'x' ? '.' : c, fp);
11737             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11738         }
11739     }
11740     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11741       fprintf(fp, "white to play\n");
11742     else
11743       fprintf(fp, "black to play\n");
11744 }
11745
11746 void
11747 PrintOpponents(fp)
11748      FILE *fp;
11749 {
11750     if (gameInfo.white != NULL) {
11751         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11752     } else {
11753         fprintf(fp, "\n");
11754     }
11755 }
11756
11757 /* Find last component of program's own name, using some heuristics */
11758 void
11759 TidyProgramName(prog, host, buf)
11760      char *prog, *host, buf[MSG_SIZ];
11761 {
11762     char *p, *q;
11763     int local = (strcmp(host, "localhost") == 0);
11764     while (!local && (p = strchr(prog, ';')) != NULL) {
11765         p++;
11766         while (*p == ' ') p++;
11767         prog = p;
11768     }
11769     if (*prog == '"' || *prog == '\'') {
11770         q = strchr(prog + 1, *prog);
11771     } else {
11772         q = strchr(prog, ' ');
11773     }
11774     if (q == NULL) q = prog + strlen(prog);
11775     p = q;
11776     while (p >= prog && *p != '/' && *p != '\\') p--;
11777     p++;
11778     if(p == prog && *p == '"') p++;
11779     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11780     memcpy(buf, p, q - p);
11781     buf[q - p] = NULLCHAR;
11782     if (!local) {
11783         strcat(buf, "@");
11784         strcat(buf, host);
11785     }
11786 }
11787
11788 char *
11789 TimeControlTagValue()
11790 {
11791     char buf[MSG_SIZ];
11792     if (!appData.clockMode) {
11793         strcpy(buf, "-");
11794     } else if (movesPerSession > 0) {
11795         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11796     } else if (timeIncrement == 0) {
11797         sprintf(buf, "%ld", timeControl/1000);
11798     } else {
11799         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11800     }
11801     return StrSave(buf);
11802 }
11803
11804 void
11805 SetGameInfo()
11806 {
11807     /* This routine is used only for certain modes */
11808     VariantClass v = gameInfo.variant;
11809     ClearGameInfo(&gameInfo);
11810     gameInfo.variant = v;
11811
11812     switch (gameMode) {
11813       case MachinePlaysWhite:
11814         gameInfo.event = StrSave( appData.pgnEventHeader );
11815         gameInfo.site = StrSave(HostName());
11816         gameInfo.date = PGNDate();
11817         gameInfo.round = StrSave("-");
11818         gameInfo.white = StrSave(first.tidy);
11819         gameInfo.black = StrSave(UserName());
11820         gameInfo.timeControl = TimeControlTagValue();
11821         break;
11822
11823       case MachinePlaysBlack:
11824         gameInfo.event = StrSave( appData.pgnEventHeader );
11825         gameInfo.site = StrSave(HostName());
11826         gameInfo.date = PGNDate();
11827         gameInfo.round = StrSave("-");
11828         gameInfo.white = StrSave(UserName());
11829         gameInfo.black = StrSave(first.tidy);
11830         gameInfo.timeControl = TimeControlTagValue();
11831         break;
11832
11833       case TwoMachinesPlay:
11834         gameInfo.event = StrSave( appData.pgnEventHeader );
11835         gameInfo.site = StrSave(HostName());
11836         gameInfo.date = PGNDate();
11837         if (matchGame > 0) {
11838             char buf[MSG_SIZ];
11839             sprintf(buf, "%d", matchGame);
11840             gameInfo.round = StrSave(buf);
11841         } else {
11842             gameInfo.round = StrSave("-");
11843         }
11844         if (first.twoMachinesColor[0] == 'w') {
11845             gameInfo.white = StrSave(first.tidy);
11846             gameInfo.black = StrSave(second.tidy);
11847         } else {
11848             gameInfo.white = StrSave(second.tidy);
11849             gameInfo.black = StrSave(first.tidy);
11850         }
11851         gameInfo.timeControl = TimeControlTagValue();
11852         break;
11853
11854       case EditGame:
11855         gameInfo.event = StrSave("Edited game");
11856         gameInfo.site = StrSave(HostName());
11857         gameInfo.date = PGNDate();
11858         gameInfo.round = StrSave("-");
11859         gameInfo.white = StrSave("-");
11860         gameInfo.black = StrSave("-");
11861         break;
11862
11863       case EditPosition:
11864         gameInfo.event = StrSave("Edited position");
11865         gameInfo.site = StrSave(HostName());
11866         gameInfo.date = PGNDate();
11867         gameInfo.round = StrSave("-");
11868         gameInfo.white = StrSave("-");
11869         gameInfo.black = StrSave("-");
11870         break;
11871
11872       case IcsPlayingWhite:
11873       case IcsPlayingBlack:
11874       case IcsObserving:
11875       case IcsExamining:
11876         break;
11877
11878       case PlayFromGameFile:
11879         gameInfo.event = StrSave("Game from non-PGN file");
11880         gameInfo.site = StrSave(HostName());
11881         gameInfo.date = PGNDate();
11882         gameInfo.round = StrSave("-");
11883         gameInfo.white = StrSave("?");
11884         gameInfo.black = StrSave("?");
11885         break;
11886
11887       default:
11888         break;
11889     }
11890 }
11891
11892 void
11893 ReplaceComment(index, text)
11894      int index;
11895      char *text;
11896 {
11897     int len;
11898
11899     while (*text == '\n') text++;
11900     len = strlen(text);
11901     while (len > 0 && text[len - 1] == '\n') len--;
11902
11903     if (commentList[index] != NULL)
11904       free(commentList[index]);
11905
11906     if (len == 0) {
11907         commentList[index] = NULL;
11908         return;
11909     }
11910     commentList[index] = (char *) malloc(len + 2);
11911     strncpy(commentList[index], text, len);
11912     commentList[index][len] = '\n';
11913     commentList[index][len + 1] = NULLCHAR;
11914 }
11915
11916 void
11917 CrushCRs(text)
11918      char *text;
11919 {
11920   char *p = text;
11921   char *q = text;
11922   char ch;
11923
11924   do {
11925     ch = *p++;
11926     if (ch == '\r') continue;
11927     *q++ = ch;
11928   } while (ch != '\0');
11929 }
11930
11931 void
11932 AppendComment(index, text)
11933      int index;
11934      char *text;
11935 {
11936     int oldlen, len;
11937     char *old;
11938
11939     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11940
11941     CrushCRs(text);
11942     while (*text == '\n') text++;
11943     len = strlen(text);
11944     while (len > 0 && text[len - 1] == '\n') len--;
11945
11946     if (len == 0) return;
11947
11948     if (commentList[index] != NULL) {
11949         old = commentList[index];
11950         oldlen = strlen(old);
11951         commentList[index] = (char *) malloc(oldlen + len + 2);
11952         strcpy(commentList[index], old);
11953         free(old);
11954         strncpy(&commentList[index][oldlen], text, len);
11955         commentList[index][oldlen + len] = '\n';
11956         commentList[index][oldlen + len + 1] = NULLCHAR;
11957     } else {
11958         commentList[index] = (char *) malloc(len + 2);
11959         strncpy(commentList[index], text, len);
11960         commentList[index][len] = '\n';
11961         commentList[index][len + 1] = NULLCHAR;
11962     }
11963 }
11964
11965 static char * FindStr( char * text, char * sub_text )
11966 {
11967     char * result = strstr( text, sub_text );
11968
11969     if( result != NULL ) {
11970         result += strlen( sub_text );
11971     }
11972
11973     return result;
11974 }
11975
11976 /* [AS] Try to extract PV info from PGN comment */
11977 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11978 char *GetInfoFromComment( int index, char * text )
11979 {
11980     char * sep = text;
11981
11982     if( text != NULL && index > 0 ) {
11983         int score = 0;
11984         int depth = 0;
11985         int time = -1, sec = 0, deci;
11986         char * s_eval = FindStr( text, "[%eval " );
11987         char * s_emt = FindStr( text, "[%emt " );
11988
11989         if( s_eval != NULL || s_emt != NULL ) {
11990             /* New style */
11991             char delim;
11992
11993             if( s_eval != NULL ) {
11994                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11995                     return text;
11996                 }
11997
11998                 if( delim != ']' ) {
11999                     return text;
12000                 }
12001             }
12002
12003             if( s_emt != NULL ) {
12004             }
12005         }
12006         else {
12007             /* We expect something like: [+|-]nnn.nn/dd */
12008             int score_lo = 0;
12009
12010             sep = strchr( text, '/' );
12011             if( sep == NULL || sep < (text+4) ) {
12012                 return text;
12013             }
12014
12015             time = -1; sec = -1; deci = -1;
12016             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12017                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12018                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12019                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12020                 return text;
12021             }
12022
12023             if( score_lo < 0 || score_lo >= 100 ) {
12024                 return text;
12025             }
12026
12027             if(sec >= 0) time = 600*time + 10*sec; else
12028             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12029
12030             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12031
12032             /* [HGM] PV time: now locate end of PV info */
12033             while( *++sep >= '0' && *sep <= '9'); // strip depth
12034             if(time >= 0)
12035             while( *++sep >= '0' && *sep <= '9'); // strip time
12036             if(sec >= 0)
12037             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12038             if(deci >= 0)
12039             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12040             while(*sep == ' ') sep++;
12041         }
12042
12043         if( depth <= 0 ) {
12044             return text;
12045         }
12046
12047         if( time < 0 ) {
12048             time = -1;
12049         }
12050
12051         pvInfoList[index-1].depth = depth;
12052         pvInfoList[index-1].score = score;
12053         pvInfoList[index-1].time  = 10*time; // centi-sec
12054     }
12055     return sep;
12056 }
12057
12058 void
12059 SendToProgram(message, cps)
12060      char *message;
12061      ChessProgramState *cps;
12062 {
12063     int count, outCount, error;
12064     char buf[MSG_SIZ];
12065
12066     if (cps->pr == NULL) return;
12067     Attention(cps);
12068     
12069     if (appData.debugMode) {
12070         TimeMark now;
12071         GetTimeMark(&now);
12072         fprintf(debugFP, "%ld >%-6s: %s", 
12073                 SubtractTimeMarks(&now, &programStartTime),
12074                 cps->which, message);
12075     }
12076     
12077     count = strlen(message);
12078     outCount = OutputToProcess(cps->pr, message, count, &error);
12079     if (outCount < count && !exiting 
12080                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12081         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12082         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12083             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12084                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12085                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12086             } else {
12087                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12088             }
12089             gameInfo.resultDetails = buf;
12090         }
12091         DisplayFatalError(buf, error, 1);
12092     }
12093 }
12094
12095 void
12096 ReceiveFromProgram(isr, closure, message, count, error)
12097      InputSourceRef isr;
12098      VOIDSTAR closure;
12099      char *message;
12100      int count;
12101      int error;
12102 {
12103     char *end_str;
12104     char buf[MSG_SIZ];
12105     ChessProgramState *cps = (ChessProgramState *)closure;
12106
12107     if (isr != cps->isr) return; /* Killed intentionally */
12108     if (count <= 0) {
12109         if (count == 0) {
12110             sprintf(buf,
12111                     _("Error: %s chess program (%s) exited unexpectedly"),
12112                     cps->which, cps->program);
12113         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12114                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12115                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12116                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12117                 } else {
12118                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12119                 }
12120                 gameInfo.resultDetails = buf;
12121             }
12122             RemoveInputSource(cps->isr);
12123             DisplayFatalError(buf, 0, 1);
12124         } else {
12125             sprintf(buf,
12126                     _("Error reading from %s chess program (%s)"),
12127                     cps->which, cps->program);
12128             RemoveInputSource(cps->isr);
12129
12130             /* [AS] Program is misbehaving badly... kill it */
12131             if( count == -2 ) {
12132                 DestroyChildProcess( cps->pr, 9 );
12133                 cps->pr = NoProc;
12134             }
12135
12136             DisplayFatalError(buf, error, 1);
12137         }
12138         return;
12139     }
12140     
12141     if ((end_str = strchr(message, '\r')) != NULL)
12142       *end_str = NULLCHAR;
12143     if ((end_str = strchr(message, '\n')) != NULL)
12144       *end_str = NULLCHAR;
12145     
12146     if (appData.debugMode) {
12147         TimeMark now; int print = 1;
12148         char *quote = ""; char c; int i;
12149
12150         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12151                 char start = message[0];
12152                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12153                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12154                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12155                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12156                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12157                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12158                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12159                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12160                         { quote = "# "; print = (appData.engineComments == 2); }
12161                 message[0] = start; // restore original message
12162         }
12163         if(print) {
12164                 GetTimeMark(&now);
12165                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12166                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12167                         quote,
12168                         message);
12169         }
12170     }
12171
12172     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12173     if (appData.icsEngineAnalyze) {
12174         if (strstr(message, "whisper") != NULL ||
12175              strstr(message, "kibitz") != NULL || 
12176             strstr(message, "tellics") != NULL) return;
12177     }
12178
12179     HandleMachineMove(message, cps);
12180 }
12181
12182
12183 void
12184 SendTimeControl(cps, mps, tc, inc, sd, st)
12185      ChessProgramState *cps;
12186      int mps, inc, sd, st;
12187      long tc;
12188 {
12189     char buf[MSG_SIZ];
12190     int seconds;
12191
12192     if( timeControl_2 > 0 ) {
12193         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12194             tc = timeControl_2;
12195         }
12196     }
12197     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12198     inc /= cps->timeOdds;
12199     st  /= cps->timeOdds;
12200
12201     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12202
12203     if (st > 0) {
12204       /* Set exact time per move, normally using st command */
12205       if (cps->stKludge) {
12206         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12207         seconds = st % 60;
12208         if (seconds == 0) {
12209           sprintf(buf, "level 1 %d\n", st/60);
12210         } else {
12211           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12212         }
12213       } else {
12214         sprintf(buf, "st %d\n", st);
12215       }
12216     } else {
12217       /* Set conventional or incremental time control, using level command */
12218       if (seconds == 0) {
12219         /* Note old gnuchess bug -- minutes:seconds used to not work.
12220            Fixed in later versions, but still avoid :seconds
12221            when seconds is 0. */
12222         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12223       } else {
12224         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12225                 seconds, inc/1000);
12226       }
12227     }
12228     SendToProgram(buf, cps);
12229
12230     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12231     /* Orthogonally, limit search to given depth */
12232     if (sd > 0) {
12233       if (cps->sdKludge) {
12234         sprintf(buf, "depth\n%d\n", sd);
12235       } else {
12236         sprintf(buf, "sd %d\n", sd);
12237       }
12238       SendToProgram(buf, cps);
12239     }
12240
12241     if(cps->nps > 0) { /* [HGM] nps */
12242         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12243         else {
12244                 sprintf(buf, "nps %d\n", cps->nps);
12245               SendToProgram(buf, cps);
12246         }
12247     }
12248 }
12249
12250 ChessProgramState *WhitePlayer()
12251 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12252 {
12253     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12254        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12255         return &second;
12256     return &first;
12257 }
12258
12259 void
12260 SendTimeRemaining(cps, machineWhite)
12261      ChessProgramState *cps;
12262      int /*boolean*/ machineWhite;
12263 {
12264     char message[MSG_SIZ];
12265     long time, otime;
12266
12267     /* Note: this routine must be called when the clocks are stopped
12268        or when they have *just* been set or switched; otherwise
12269        it will be off by the time since the current tick started.
12270     */
12271     if (machineWhite) {
12272         time = whiteTimeRemaining / 10;
12273         otime = blackTimeRemaining / 10;
12274     } else {
12275         time = blackTimeRemaining / 10;
12276         otime = whiteTimeRemaining / 10;
12277     }
12278     /* [HGM] translate opponent's time by time-odds factor */
12279     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12280     if (appData.debugMode) {
12281         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12282     }
12283
12284     if (time <= 0) time = 1;
12285     if (otime <= 0) otime = 1;
12286     
12287     sprintf(message, "time %ld\n", time);
12288     SendToProgram(message, cps);
12289
12290     sprintf(message, "otim %ld\n", otime);
12291     SendToProgram(message, cps);
12292 }
12293
12294 int
12295 BoolFeature(p, name, loc, cps)
12296      char **p;
12297      char *name;
12298      int *loc;
12299      ChessProgramState *cps;
12300 {
12301   char buf[MSG_SIZ];
12302   int len = strlen(name);
12303   int val;
12304   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12305     (*p) += len + 1;
12306     sscanf(*p, "%d", &val);
12307     *loc = (val != 0);
12308     while (**p && **p != ' ') (*p)++;
12309     sprintf(buf, "accepted %s\n", name);
12310     SendToProgram(buf, cps);
12311     return TRUE;
12312   }
12313   return FALSE;
12314 }
12315
12316 int
12317 IntFeature(p, name, loc, cps)
12318      char **p;
12319      char *name;
12320      int *loc;
12321      ChessProgramState *cps;
12322 {
12323   char buf[MSG_SIZ];
12324   int len = strlen(name);
12325   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12326     (*p) += len + 1;
12327     sscanf(*p, "%d", loc);
12328     while (**p && **p != ' ') (*p)++;
12329     sprintf(buf, "accepted %s\n", name);
12330     SendToProgram(buf, cps);
12331     return TRUE;
12332   }
12333   return FALSE;
12334 }
12335
12336 int
12337 StringFeature(p, name, loc, cps)
12338      char **p;
12339      char *name;
12340      char loc[];
12341      ChessProgramState *cps;
12342 {
12343   char buf[MSG_SIZ];
12344   int len = strlen(name);
12345   if (strncmp((*p), name, len) == 0
12346       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12347     (*p) += len + 2;
12348     sscanf(*p, "%[^\"]", loc);
12349     while (**p && **p != '\"') (*p)++;
12350     if (**p == '\"') (*p)++;
12351     sprintf(buf, "accepted %s\n", name);
12352     SendToProgram(buf, cps);
12353     return TRUE;
12354   }
12355   return FALSE;
12356 }
12357
12358 int 
12359 ParseOption(Option *opt, ChessProgramState *cps)
12360 // [HGM] options: process the string that defines an engine option, and determine
12361 // name, type, default value, and allowed value range
12362 {
12363         char *p, *q, buf[MSG_SIZ];
12364         int n, min = (-1)<<31, max = 1<<31, def;
12365
12366         if(p = strstr(opt->name, " -spin ")) {
12367             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12368             if(max < min) max = min; // enforce consistency
12369             if(def < min) def = min;
12370             if(def > max) def = max;
12371             opt->value = def;
12372             opt->min = min;
12373             opt->max = max;
12374             opt->type = Spin;
12375         } else if((p = strstr(opt->name, " -slider "))) {
12376             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12377             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12378             if(max < min) max = min; // enforce consistency
12379             if(def < min) def = min;
12380             if(def > max) def = max;
12381             opt->value = def;
12382             opt->min = min;
12383             opt->max = max;
12384             opt->type = Spin; // Slider;
12385         } else if((p = strstr(opt->name, " -string "))) {
12386             opt->textValue = p+9;
12387             opt->type = TextBox;
12388         } else if((p = strstr(opt->name, " -file "))) {
12389             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12390             opt->textValue = p+7;
12391             opt->type = TextBox; // FileName;
12392         } else if((p = strstr(opt->name, " -path "))) {
12393             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12394             opt->textValue = p+7;
12395             opt->type = TextBox; // PathName;
12396         } else if(p = strstr(opt->name, " -check ")) {
12397             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12398             opt->value = (def != 0);
12399             opt->type = CheckBox;
12400         } else if(p = strstr(opt->name, " -combo ")) {
12401             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12402             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12403             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12404             opt->value = n = 0;
12405             while(q = StrStr(q, " /// ")) {
12406                 n++; *q = 0;    // count choices, and null-terminate each of them
12407                 q += 5;
12408                 if(*q == '*') { // remember default, which is marked with * prefix
12409                     q++;
12410                     opt->value = n;
12411                 }
12412                 cps->comboList[cps->comboCnt++] = q;
12413             }
12414             cps->comboList[cps->comboCnt++] = NULL;
12415             opt->max = n + 1;
12416             opt->type = ComboBox;
12417         } else if(p = strstr(opt->name, " -button")) {
12418             opt->type = Button;
12419         } else if(p = strstr(opt->name, " -save")) {
12420             opt->type = SaveButton;
12421         } else return FALSE;
12422         *p = 0; // terminate option name
12423         // now look if the command-line options define a setting for this engine option.
12424         if(cps->optionSettings && cps->optionSettings[0])
12425             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12426         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12427                 sprintf(buf, "option %s", p);
12428                 if(p = strstr(buf, ",")) *p = 0;
12429                 strcat(buf, "\n");
12430                 SendToProgram(buf, cps);
12431         }
12432         return TRUE;
12433 }
12434
12435 void
12436 FeatureDone(cps, val)
12437      ChessProgramState* cps;
12438      int val;
12439 {
12440   DelayedEventCallback cb = GetDelayedEvent();
12441   if ((cb == InitBackEnd3 && cps == &first) ||
12442       (cb == TwoMachinesEventIfReady && cps == &second)) {
12443     CancelDelayedEvent();
12444     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12445   }
12446   cps->initDone = val;
12447 }
12448
12449 /* Parse feature command from engine */
12450 void
12451 ParseFeatures(args, cps)
12452      char* args;
12453      ChessProgramState *cps;  
12454 {
12455   char *p = args;
12456   char *q;
12457   int val;
12458   char buf[MSG_SIZ];
12459
12460   for (;;) {
12461     while (*p == ' ') p++;
12462     if (*p == NULLCHAR) return;
12463
12464     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12465     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12466     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12467     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12468     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12469     if (BoolFeature(&p, "reuse", &val, cps)) {
12470       /* Engine can disable reuse, but can't enable it if user said no */
12471       if (!val) cps->reuse = FALSE;
12472       continue;
12473     }
12474     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12475     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12476       if (gameMode == TwoMachinesPlay) {
12477         DisplayTwoMachinesTitle();
12478       } else {
12479         DisplayTitle("");
12480       }
12481       continue;
12482     }
12483     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12484     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12485     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12486     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12487     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12488     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12489     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12490     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12491     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12492     if (IntFeature(&p, "done", &val, cps)) {
12493       FeatureDone(cps, val);
12494       continue;
12495     }
12496     /* Added by Tord: */
12497     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12498     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12499     /* End of additions by Tord */
12500
12501     /* [HGM] added features: */
12502     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12503     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12504     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12505     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12506     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12507     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12508     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12509         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12510             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12511             SendToProgram(buf, cps);
12512             continue;
12513         }
12514         if(cps->nrOptions >= MAX_OPTIONS) {
12515             cps->nrOptions--;
12516             sprintf(buf, "%s engine has too many options\n", cps->which);
12517             DisplayError(buf, 0);
12518         }
12519         continue;
12520     }
12521     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12522     /* End of additions by HGM */
12523
12524     /* unknown feature: complain and skip */
12525     q = p;
12526     while (*q && *q != '=') q++;
12527     sprintf(buf, "rejected %.*s\n", q-p, p);
12528     SendToProgram(buf, cps);
12529     p = q;
12530     if (*p == '=') {
12531       p++;
12532       if (*p == '\"') {
12533         p++;
12534         while (*p && *p != '\"') p++;
12535         if (*p == '\"') p++;
12536       } else {
12537         while (*p && *p != ' ') p++;
12538       }
12539     }
12540   }
12541
12542 }
12543
12544 void
12545 PeriodicUpdatesEvent(newState)
12546      int newState;
12547 {
12548     if (newState == appData.periodicUpdates)
12549       return;
12550
12551     appData.periodicUpdates=newState;
12552
12553     /* Display type changes, so update it now */
12554     DisplayAnalysis();
12555
12556     /* Get the ball rolling again... */
12557     if (newState) {
12558         AnalysisPeriodicEvent(1);
12559         StartAnalysisClock();
12560     }
12561 }
12562
12563 void
12564 PonderNextMoveEvent(newState)
12565      int newState;
12566 {
12567     if (newState == appData.ponderNextMove) return;
12568     if (gameMode == EditPosition) EditPositionDone();
12569     if (newState) {
12570         SendToProgram("hard\n", &first);
12571         if (gameMode == TwoMachinesPlay) {
12572             SendToProgram("hard\n", &second);
12573         }
12574     } else {
12575         SendToProgram("easy\n", &first);
12576         thinkOutput[0] = NULLCHAR;
12577         if (gameMode == TwoMachinesPlay) {
12578             SendToProgram("easy\n", &second);
12579         }
12580     }
12581     appData.ponderNextMove = newState;
12582 }
12583
12584 void
12585 NewSettingEvent(option, command, value)
12586      char *command;
12587      int option, value;
12588 {
12589     char buf[MSG_SIZ];
12590
12591     if (gameMode == EditPosition) EditPositionDone();
12592     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12593     SendToProgram(buf, &first);
12594     if (gameMode == TwoMachinesPlay) {
12595         SendToProgram(buf, &second);
12596     }
12597 }
12598
12599 void
12600 ShowThinkingEvent()
12601 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12602 {
12603     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12604     int newState = appData.showThinking
12605         // [HGM] thinking: other features now need thinking output as well
12606         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12607     
12608     if (oldState == newState) return;
12609     oldState = newState;
12610     if (gameMode == EditPosition) EditPositionDone();
12611     if (oldState) {
12612         SendToProgram("post\n", &first);
12613         if (gameMode == TwoMachinesPlay) {
12614             SendToProgram("post\n", &second);
12615         }
12616     } else {
12617         SendToProgram("nopost\n", &first);
12618         thinkOutput[0] = NULLCHAR;
12619         if (gameMode == TwoMachinesPlay) {
12620             SendToProgram("nopost\n", &second);
12621         }
12622     }
12623 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12624 }
12625
12626 void
12627 AskQuestionEvent(title, question, replyPrefix, which)
12628      char *title; char *question; char *replyPrefix; char *which;
12629 {
12630   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12631   if (pr == NoProc) return;
12632   AskQuestion(title, question, replyPrefix, pr);
12633 }
12634
12635 void
12636 DisplayMove(moveNumber)
12637      int moveNumber;
12638 {
12639     char message[MSG_SIZ];
12640     char res[MSG_SIZ];
12641     char cpThinkOutput[MSG_SIZ];
12642
12643     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12644     
12645     if (moveNumber == forwardMostMove - 1 || 
12646         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12647
12648         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12649
12650         if (strchr(cpThinkOutput, '\n')) {
12651             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12652         }
12653     } else {
12654         *cpThinkOutput = NULLCHAR;
12655     }
12656
12657     /* [AS] Hide thinking from human user */
12658     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12659         *cpThinkOutput = NULLCHAR;
12660         if( thinkOutput[0] != NULLCHAR ) {
12661             int i;
12662
12663             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12664                 cpThinkOutput[i] = '.';
12665             }
12666             cpThinkOutput[i] = NULLCHAR;
12667             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12668         }
12669     }
12670
12671     if (moveNumber == forwardMostMove - 1 &&
12672         gameInfo.resultDetails != NULL) {
12673         if (gameInfo.resultDetails[0] == NULLCHAR) {
12674             sprintf(res, " %s", PGNResult(gameInfo.result));
12675         } else {
12676             sprintf(res, " {%s} %s",
12677                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12678         }
12679     } else {
12680         res[0] = NULLCHAR;
12681     }
12682
12683     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12684         DisplayMessage(res, cpThinkOutput);
12685     } else {
12686         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12687                 WhiteOnMove(moveNumber) ? " " : ".. ",
12688                 parseList[moveNumber], res);
12689         DisplayMessage(message, cpThinkOutput);
12690     }
12691 }
12692
12693 void
12694 DisplayAnalysisText(text)
12695      char *text;
12696 {
12697     char buf[MSG_SIZ];
12698
12699     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12700                || appData.icsEngineAnalyze) {
12701         sprintf(buf, "Analysis (%s)", first.tidy);
12702         AnalysisPopUp(buf, text);
12703     }
12704 }
12705
12706 static int
12707 only_one_move(str)
12708      char *str;
12709 {
12710     while (*str && isspace(*str)) ++str;
12711     while (*str && !isspace(*str)) ++str;
12712     if (!*str) return 1;
12713     while (*str && isspace(*str)) ++str;
12714     if (!*str) return 1;
12715     return 0;
12716 }
12717
12718 void
12719 DisplayAnalysis()
12720 {
12721     char buf[MSG_SIZ];
12722     char lst[MSG_SIZ / 2];
12723     double nps;
12724     static char *xtra[] = { "", " (--)", " (++)" };
12725     int h, m, s, cs;
12726   
12727     if (programStats.time == 0) {
12728         programStats.time = 1;
12729     }
12730   
12731     if (programStats.got_only_move) {
12732         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12733     } else {
12734         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12735
12736         nps = (u64ToDouble(programStats.nodes) /
12737              ((double)programStats.time /100.0));
12738
12739         cs = programStats.time % 100;
12740         s = programStats.time / 100;
12741         h = (s / (60*60));
12742         s = s - h*60*60;
12743         m = (s/60);
12744         s = s - m*60;
12745
12746         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12747           if (programStats.move_name[0] != NULLCHAR) {
12748             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12749                     programStats.depth,
12750                     programStats.nr_moves-programStats.moves_left,
12751                     programStats.nr_moves, programStats.move_name,
12752                     ((float)programStats.score)/100.0, lst,
12753                     only_one_move(lst)?
12754                     xtra[programStats.got_fail] : "",
12755                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12756           } else {
12757             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12758                     programStats.depth,
12759                     programStats.nr_moves-programStats.moves_left,
12760                     programStats.nr_moves, ((float)programStats.score)/100.0,
12761                     lst,
12762                     only_one_move(lst)?
12763                     xtra[programStats.got_fail] : "",
12764                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12765           }
12766         } else {
12767             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12768                     programStats.depth,
12769                     ((float)programStats.score)/100.0,
12770                     lst,
12771                     only_one_move(lst)?
12772                     xtra[programStats.got_fail] : "",
12773                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12774         }
12775     }
12776     DisplayAnalysisText(buf);
12777 }
12778
12779 void
12780 DisplayComment(moveNumber, text)
12781      int moveNumber;
12782      char *text;
12783 {
12784     char title[MSG_SIZ];
12785     char buf[8000]; // comment can be long!
12786     int score, depth;
12787
12788     if( appData.autoDisplayComment ) {
12789         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12790             strcpy(title, "Comment");
12791         } else {
12792             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12793                     WhiteOnMove(moveNumber) ? " " : ".. ",
12794                     parseList[moveNumber]);
12795         }
12796         // [HGM] PV info: display PV info together with (or as) comment
12797         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12798             if(text == NULL) text = "";                                           
12799             score = pvInfoList[moveNumber].score;
12800             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12801                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12802             text = buf;
12803         }
12804     } else title[0] = 0;
12805
12806     if (text != NULL)
12807         CommentPopUp(title, text);
12808 }
12809
12810 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12811  * might be busy thinking or pondering.  It can be omitted if your
12812  * gnuchess is configured to stop thinking immediately on any user
12813  * input.  However, that gnuchess feature depends on the FIONREAD
12814  * ioctl, which does not work properly on some flavors of Unix.
12815  */
12816 void
12817 Attention(cps)
12818      ChessProgramState *cps;
12819 {
12820 #if ATTENTION
12821     if (!cps->useSigint) return;
12822     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12823     switch (gameMode) {
12824       case MachinePlaysWhite:
12825       case MachinePlaysBlack:
12826       case TwoMachinesPlay:
12827       case IcsPlayingWhite:
12828       case IcsPlayingBlack:
12829       case AnalyzeMode:
12830       case AnalyzeFile:
12831         /* Skip if we know it isn't thinking */
12832         if (!cps->maybeThinking) return;
12833         if (appData.debugMode)
12834           fprintf(debugFP, "Interrupting %s\n", cps->which);
12835         InterruptChildProcess(cps->pr);
12836         cps->maybeThinking = FALSE;
12837         break;
12838       default:
12839         break;
12840     }
12841 #endif /*ATTENTION*/
12842 }
12843
12844 int
12845 CheckFlags()
12846 {
12847     if (whiteTimeRemaining <= 0) {
12848         if (!whiteFlag) {
12849             whiteFlag = TRUE;
12850             if (appData.icsActive) {
12851                 if (appData.autoCallFlag &&
12852                     gameMode == IcsPlayingBlack && !blackFlag) {
12853                   SendToICS(ics_prefix);
12854                   SendToICS("flag\n");
12855                 }
12856             } else {
12857                 if (blackFlag) {
12858                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12859                 } else {
12860                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12861                     if (appData.autoCallFlag) {
12862                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12863                         return TRUE;
12864                     }
12865                 }
12866             }
12867         }
12868     }
12869     if (blackTimeRemaining <= 0) {
12870         if (!blackFlag) {
12871             blackFlag = TRUE;
12872             if (appData.icsActive) {
12873                 if (appData.autoCallFlag &&
12874                     gameMode == IcsPlayingWhite && !whiteFlag) {
12875                   SendToICS(ics_prefix);
12876                   SendToICS("flag\n");
12877                 }
12878             } else {
12879                 if (whiteFlag) {
12880                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12881                 } else {
12882                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12883                     if (appData.autoCallFlag) {
12884                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12885                         return TRUE;
12886                     }
12887                 }
12888             }
12889         }
12890     }
12891     return FALSE;
12892 }
12893
12894 void
12895 CheckTimeControl()
12896 {
12897     if (!appData.clockMode || appData.icsActive ||
12898         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12899
12900     /*
12901      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12902      */
12903     if ( !WhiteOnMove(forwardMostMove) )
12904         /* White made time control */
12905         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12906         /* [HGM] time odds: correct new time quota for time odds! */
12907                                             / WhitePlayer()->timeOdds;
12908       else
12909         /* Black made time control */
12910         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12911                                             / WhitePlayer()->other->timeOdds;
12912 }
12913
12914 void
12915 DisplayBothClocks()
12916 {
12917     int wom = gameMode == EditPosition ?
12918       !blackPlaysFirst : WhiteOnMove(currentMove);
12919     DisplayWhiteClock(whiteTimeRemaining, wom);
12920     DisplayBlackClock(blackTimeRemaining, !wom);
12921 }
12922
12923
12924 /* Timekeeping seems to be a portability nightmare.  I think everyone
12925    has ftime(), but I'm really not sure, so I'm including some ifdefs
12926    to use other calls if you don't.  Clocks will be less accurate if
12927    you have neither ftime nor gettimeofday.
12928 */
12929
12930 /* VS 2008 requires the #include outside of the function */
12931 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12932 #include <sys/timeb.h>
12933 #endif
12934
12935 /* Get the current time as a TimeMark */
12936 void
12937 GetTimeMark(tm)
12938      TimeMark *tm;
12939 {
12940 #if HAVE_GETTIMEOFDAY
12941
12942     struct timeval timeVal;
12943     struct timezone timeZone;
12944
12945     gettimeofday(&timeVal, &timeZone);
12946     tm->sec = (long) timeVal.tv_sec; 
12947     tm->ms = (int) (timeVal.tv_usec / 1000L);
12948
12949 #else /*!HAVE_GETTIMEOFDAY*/
12950 #if HAVE_FTIME
12951
12952 // include <sys/timeb.h> / moved to just above start of function
12953     struct timeb timeB;
12954
12955     ftime(&timeB);
12956     tm->sec = (long) timeB.time;
12957     tm->ms = (int) timeB.millitm;
12958
12959 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12960     tm->sec = (long) time(NULL);
12961     tm->ms = 0;
12962 #endif
12963 #endif
12964 }
12965
12966 /* Return the difference in milliseconds between two
12967    time marks.  We assume the difference will fit in a long!
12968 */
12969 long
12970 SubtractTimeMarks(tm2, tm1)
12971      TimeMark *tm2, *tm1;
12972 {
12973     return 1000L*(tm2->sec - tm1->sec) +
12974            (long) (tm2->ms - tm1->ms);
12975 }
12976
12977
12978 /*
12979  * Code to manage the game clocks.
12980  *
12981  * In tournament play, black starts the clock and then white makes a move.
12982  * We give the human user a slight advantage if he is playing white---the
12983  * clocks don't run until he makes his first move, so it takes zero time.
12984  * Also, we don't account for network lag, so we could get out of sync
12985  * with GNU Chess's clock -- but then, referees are always right.  
12986  */
12987
12988 static TimeMark tickStartTM;
12989 static long intendedTickLength;
12990
12991 long
12992 NextTickLength(timeRemaining)
12993      long timeRemaining;
12994 {
12995     long nominalTickLength, nextTickLength;
12996
12997     if (timeRemaining > 0L && timeRemaining <= 10000L)
12998       nominalTickLength = 100L;
12999     else
13000       nominalTickLength = 1000L;
13001     nextTickLength = timeRemaining % nominalTickLength;
13002     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13003
13004     return nextTickLength;
13005 }
13006
13007 /* Adjust clock one minute up or down */
13008 void
13009 AdjustClock(Boolean which, int dir)
13010 {
13011     if(which) blackTimeRemaining += 60000*dir;
13012     else      whiteTimeRemaining += 60000*dir;
13013     DisplayBothClocks();
13014 }
13015
13016 /* Stop clocks and reset to a fresh time control */
13017 void
13018 ResetClocks() 
13019 {
13020     (void) StopClockTimer();
13021     if (appData.icsActive) {
13022         whiteTimeRemaining = blackTimeRemaining = 0;
13023     } else { /* [HGM] correct new time quote for time odds */
13024         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13025         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13026     }
13027     if (whiteFlag || blackFlag) {
13028         DisplayTitle("");
13029         whiteFlag = blackFlag = FALSE;
13030     }
13031     DisplayBothClocks();
13032 }
13033
13034 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13035
13036 /* Decrement running clock by amount of time that has passed */
13037 void
13038 DecrementClocks()
13039 {
13040     long timeRemaining;
13041     long lastTickLength, fudge;
13042     TimeMark now;
13043
13044     if (!appData.clockMode) return;
13045     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13046         
13047     GetTimeMark(&now);
13048
13049     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13050
13051     /* Fudge if we woke up a little too soon */
13052     fudge = intendedTickLength - lastTickLength;
13053     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13054
13055     if (WhiteOnMove(forwardMostMove)) {
13056         if(whiteNPS >= 0) lastTickLength = 0;
13057         timeRemaining = whiteTimeRemaining -= lastTickLength;
13058         DisplayWhiteClock(whiteTimeRemaining - fudge,
13059                           WhiteOnMove(currentMove));
13060     } else {
13061         if(blackNPS >= 0) lastTickLength = 0;
13062         timeRemaining = blackTimeRemaining -= lastTickLength;
13063         DisplayBlackClock(blackTimeRemaining - fudge,
13064                           !WhiteOnMove(currentMove));
13065     }
13066
13067     if (CheckFlags()) return;
13068         
13069     tickStartTM = now;
13070     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13071     StartClockTimer(intendedTickLength);
13072
13073     /* if the time remaining has fallen below the alarm threshold, sound the
13074      * alarm. if the alarm has sounded and (due to a takeback or time control
13075      * with increment) the time remaining has increased to a level above the
13076      * threshold, reset the alarm so it can sound again. 
13077      */
13078     
13079     if (appData.icsActive && appData.icsAlarm) {
13080
13081         /* make sure we are dealing with the user's clock */
13082         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13083                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13084            )) return;
13085
13086         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13087             alarmSounded = FALSE;
13088         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13089             PlayAlarmSound();
13090             alarmSounded = TRUE;
13091         }
13092     }
13093 }
13094
13095
13096 /* A player has just moved, so stop the previously running
13097    clock and (if in clock mode) start the other one.
13098    We redisplay both clocks in case we're in ICS mode, because
13099    ICS gives us an update to both clocks after every move.
13100    Note that this routine is called *after* forwardMostMove
13101    is updated, so the last fractional tick must be subtracted
13102    from the color that is *not* on move now.
13103 */
13104 void
13105 SwitchClocks()
13106 {
13107     long lastTickLength;
13108     TimeMark now;
13109     int flagged = FALSE;
13110
13111     GetTimeMark(&now);
13112
13113     if (StopClockTimer() && appData.clockMode) {
13114         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13115         if (WhiteOnMove(forwardMostMove)) {
13116             if(blackNPS >= 0) lastTickLength = 0;
13117             blackTimeRemaining -= lastTickLength;
13118            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13119 //         if(pvInfoList[forwardMostMove-1].time == -1)
13120                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13121                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13122         } else {
13123            if(whiteNPS >= 0) lastTickLength = 0;
13124            whiteTimeRemaining -= lastTickLength;
13125            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13126 //         if(pvInfoList[forwardMostMove-1].time == -1)
13127                  pvInfoList[forwardMostMove-1].time = 
13128                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13129         }
13130         flagged = CheckFlags();
13131     }
13132     CheckTimeControl();
13133
13134     if (flagged || !appData.clockMode) return;
13135
13136     switch (gameMode) {
13137       case MachinePlaysBlack:
13138       case MachinePlaysWhite:
13139       case BeginningOfGame:
13140         if (pausing) return;
13141         break;
13142
13143       case EditGame:
13144       case PlayFromGameFile:
13145       case IcsExamining:
13146         return;
13147
13148       default:
13149         break;
13150     }
13151
13152     tickStartTM = now;
13153     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13154       whiteTimeRemaining : blackTimeRemaining);
13155     StartClockTimer(intendedTickLength);
13156 }
13157         
13158
13159 /* Stop both clocks */
13160 void
13161 StopClocks()
13162 {       
13163     long lastTickLength;
13164     TimeMark now;
13165
13166     if (!StopClockTimer()) return;
13167     if (!appData.clockMode) return;
13168
13169     GetTimeMark(&now);
13170
13171     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13172     if (WhiteOnMove(forwardMostMove)) {
13173         if(whiteNPS >= 0) lastTickLength = 0;
13174         whiteTimeRemaining -= lastTickLength;
13175         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13176     } else {
13177         if(blackNPS >= 0) lastTickLength = 0;
13178         blackTimeRemaining -= lastTickLength;
13179         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13180     }
13181     CheckFlags();
13182 }
13183         
13184 /* Start clock of player on move.  Time may have been reset, so
13185    if clock is already running, stop and restart it. */
13186 void
13187 StartClocks()
13188 {
13189     (void) StopClockTimer(); /* in case it was running already */
13190     DisplayBothClocks();
13191     if (CheckFlags()) return;
13192
13193     if (!appData.clockMode) return;
13194     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13195
13196     GetTimeMark(&tickStartTM);
13197     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13198       whiteTimeRemaining : blackTimeRemaining);
13199
13200    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13201     whiteNPS = blackNPS = -1; 
13202     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13203        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13204         whiteNPS = first.nps;
13205     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13206        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13207         blackNPS = first.nps;
13208     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13209         whiteNPS = second.nps;
13210     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13211         blackNPS = second.nps;
13212     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13213
13214     StartClockTimer(intendedTickLength);
13215 }
13216
13217 char *
13218 TimeString(ms)
13219      long ms;
13220 {
13221     long second, minute, hour, day;
13222     char *sign = "";
13223     static char buf[32];
13224     
13225     if (ms > 0 && ms <= 9900) {
13226       /* convert milliseconds to tenths, rounding up */
13227       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13228
13229       sprintf(buf, " %03.1f ", tenths/10.0);
13230       return buf;
13231     }
13232
13233     /* convert milliseconds to seconds, rounding up */
13234     /* use floating point to avoid strangeness of integer division
13235        with negative dividends on many machines */
13236     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13237
13238     if (second < 0) {
13239         sign = "-";
13240         second = -second;
13241     }
13242     
13243     day = second / (60 * 60 * 24);
13244     second = second % (60 * 60 * 24);
13245     hour = second / (60 * 60);
13246     second = second % (60 * 60);
13247     minute = second / 60;
13248     second = second % 60;
13249     
13250     if (day > 0)
13251       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13252               sign, day, hour, minute, second);
13253     else if (hour > 0)
13254       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13255     else
13256       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13257     
13258     return buf;
13259 }
13260
13261
13262 /*
13263  * This is necessary because some C libraries aren't ANSI C compliant yet.
13264  */
13265 char *
13266 StrStr(string, match)
13267      char *string, *match;
13268 {
13269     int i, length;
13270     
13271     length = strlen(match);
13272     
13273     for (i = strlen(string) - length; i >= 0; i--, string++)
13274       if (!strncmp(match, string, length))
13275         return string;
13276     
13277     return NULL;
13278 }
13279
13280 char *
13281 StrCaseStr(string, match)
13282      char *string, *match;
13283 {
13284     int i, j, length;
13285     
13286     length = strlen(match);
13287     
13288     for (i = strlen(string) - length; i >= 0; i--, string++) {
13289         for (j = 0; j < length; j++) {
13290             if (ToLower(match[j]) != ToLower(string[j]))
13291               break;
13292         }
13293         if (j == length) return string;
13294     }
13295
13296     return NULL;
13297 }
13298
13299 #ifndef _amigados
13300 int
13301 StrCaseCmp(s1, s2)
13302      char *s1, *s2;
13303 {
13304     char c1, c2;
13305     
13306     for (;;) {
13307         c1 = ToLower(*s1++);
13308         c2 = ToLower(*s2++);
13309         if (c1 > c2) return 1;
13310         if (c1 < c2) return -1;
13311         if (c1 == NULLCHAR) return 0;
13312     }
13313 }
13314
13315
13316 int
13317 ToLower(c)
13318      int c;
13319 {
13320     return isupper(c) ? tolower(c) : c;
13321 }
13322
13323
13324 int
13325 ToUpper(c)
13326      int c;
13327 {
13328     return islower(c) ? toupper(c) : c;
13329 }
13330 #endif /* !_amigados    */
13331
13332 char *
13333 StrSave(s)
13334      char *s;
13335 {
13336     char *ret;
13337
13338     if ((ret = (char *) malloc(strlen(s) + 1))) {
13339         strcpy(ret, s);
13340     }
13341     return ret;
13342 }
13343
13344 char *
13345 StrSavePtr(s, savePtr)
13346      char *s, **savePtr;
13347 {
13348     if (*savePtr) {
13349         free(*savePtr);
13350     }
13351     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13352         strcpy(*savePtr, s);
13353     }
13354     return(*savePtr);
13355 }
13356
13357 char *
13358 PGNDate()
13359 {
13360     time_t clock;
13361     struct tm *tm;
13362     char buf[MSG_SIZ];
13363
13364     clock = time((time_t *)NULL);
13365     tm = localtime(&clock);
13366     sprintf(buf, "%04d.%02d.%02d",
13367             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13368     return StrSave(buf);
13369 }
13370
13371
13372 char *
13373 PositionToFEN(move, overrideCastling)
13374      int move;
13375      char *overrideCastling;
13376 {
13377     int i, j, fromX, fromY, toX, toY;
13378     int whiteToPlay;
13379     char buf[128];
13380     char *p, *q;
13381     int emptycount;
13382     ChessSquare piece;
13383
13384     whiteToPlay = (gameMode == EditPosition) ?
13385       !blackPlaysFirst : (move % 2 == 0);
13386     p = buf;
13387
13388     /* Piece placement data */
13389     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13390         emptycount = 0;
13391         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13392             if (boards[move][i][j] == EmptySquare) {
13393                 emptycount++;
13394             } else { ChessSquare piece = boards[move][i][j];
13395                 if (emptycount > 0) {
13396                     if(emptycount<10) /* [HGM] can be >= 10 */
13397                         *p++ = '0' + emptycount;
13398                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13399                     emptycount = 0;
13400                 }
13401                 if(PieceToChar(piece) == '+') {
13402                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13403                     *p++ = '+';
13404                     piece = (ChessSquare)(DEMOTED piece);
13405                 } 
13406                 *p++ = PieceToChar(piece);
13407                 if(p[-1] == '~') {
13408                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13409                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13410                     *p++ = '~';
13411                 }
13412             }
13413         }
13414         if (emptycount > 0) {
13415             if(emptycount<10) /* [HGM] can be >= 10 */
13416                 *p++ = '0' + emptycount;
13417             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13418             emptycount = 0;
13419         }
13420         *p++ = '/';
13421     }
13422     *(p - 1) = ' ';
13423
13424     /* [HGM] print Crazyhouse or Shogi holdings */
13425     if( gameInfo.holdingsWidth ) {
13426         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13427         q = p;
13428         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13429             piece = boards[move][i][BOARD_WIDTH-1];
13430             if( piece != EmptySquare )
13431               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13432                   *p++ = PieceToChar(piece);
13433         }
13434         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13435             piece = boards[move][BOARD_HEIGHT-i-1][0];
13436             if( piece != EmptySquare )
13437               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13438                   *p++ = PieceToChar(piece);
13439         }
13440
13441         if( q == p ) *p++ = '-';
13442         *p++ = ']';
13443         *p++ = ' ';
13444     }
13445
13446     /* Active color */
13447     *p++ = whiteToPlay ? 'w' : 'b';
13448     *p++ = ' ';
13449
13450   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13451     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13452   } else {
13453   if(nrCastlingRights) {
13454      q = p;
13455      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13456        /* [HGM] write directly from rights */
13457            if(castlingRights[move][2] >= 0 &&
13458               castlingRights[move][0] >= 0   )
13459                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13460            if(castlingRights[move][2] >= 0 &&
13461               castlingRights[move][1] >= 0   )
13462                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13463            if(castlingRights[move][5] >= 0 &&
13464               castlingRights[move][3] >= 0   )
13465                 *p++ = castlingRights[move][3] + AAA;
13466            if(castlingRights[move][5] >= 0 &&
13467               castlingRights[move][4] >= 0   )
13468                 *p++ = castlingRights[move][4] + AAA;
13469      } else {
13470
13471         /* [HGM] write true castling rights */
13472         if( nrCastlingRights == 6 ) {
13473             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13474                castlingRights[move][2] >= 0  ) *p++ = 'K';
13475             if(castlingRights[move][1] == BOARD_LEFT &&
13476                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13477             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13478                castlingRights[move][5] >= 0  ) *p++ = 'k';
13479             if(castlingRights[move][4] == BOARD_LEFT &&
13480                castlingRights[move][5] >= 0  ) *p++ = 'q';
13481         }
13482      }
13483      if (q == p) *p++ = '-'; /* No castling rights */
13484      *p++ = ' ';
13485   }
13486
13487   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13488      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13489     /* En passant target square */
13490     if (move > backwardMostMove) {
13491         fromX = moveList[move - 1][0] - AAA;
13492         fromY = moveList[move - 1][1] - ONE;
13493         toX = moveList[move - 1][2] - AAA;
13494         toY = moveList[move - 1][3] - ONE;
13495         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13496             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13497             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13498             fromX == toX) {
13499             /* 2-square pawn move just happened */
13500             *p++ = toX + AAA;
13501             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13502         } else {
13503             *p++ = '-';
13504         }
13505     } else if(move == backwardMostMove) {
13506         // [HGM] perhaps we should always do it like this, and forget the above?
13507         if(epStatus[move] >= 0) {
13508             *p++ = epStatus[move] + AAA;
13509             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13510         } else {
13511             *p++ = '-';
13512         }
13513     } else {
13514         *p++ = '-';
13515     }
13516     *p++ = ' ';
13517   }
13518   }
13519
13520     /* [HGM] find reversible plies */
13521     {   int i = 0, j=move;
13522
13523         if (appData.debugMode) { int k;
13524             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13525             for(k=backwardMostMove; k<=forwardMostMove; k++)
13526                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13527
13528         }
13529
13530         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13531         if( j == backwardMostMove ) i += initialRulePlies;
13532         sprintf(p, "%d ", i);
13533         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13534     }
13535     /* Fullmove number */
13536     sprintf(p, "%d", (move / 2) + 1);
13537     
13538     return StrSave(buf);
13539 }
13540
13541 Boolean
13542 ParseFEN(board, blackPlaysFirst, fen)
13543     Board board;
13544      int *blackPlaysFirst;
13545      char *fen;
13546 {
13547     int i, j;
13548     char *p;
13549     int emptycount;
13550     ChessSquare piece;
13551
13552     p = fen;
13553
13554     /* [HGM] by default clear Crazyhouse holdings, if present */
13555     if(gameInfo.holdingsWidth) {
13556        for(i=0; i<BOARD_HEIGHT; i++) {
13557            board[i][0]             = EmptySquare; /* black holdings */
13558            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13559            board[i][1]             = (ChessSquare) 0; /* black counts */
13560            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13561        }
13562     }
13563
13564     /* Piece placement data */
13565     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13566         j = 0;
13567         for (;;) {
13568             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13569                 if (*p == '/') p++;
13570                 emptycount = gameInfo.boardWidth - j;
13571                 while (emptycount--)
13572                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13573                 break;
13574 #if(BOARD_SIZE >= 10)
13575             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13576                 p++; emptycount=10;
13577                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13578                 while (emptycount--)
13579                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13580 #endif
13581             } else if (isdigit(*p)) {
13582                 emptycount = *p++ - '0';
13583                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13584                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13585                 while (emptycount--)
13586                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13587             } else if (*p == '+' || isalpha(*p)) {
13588                 if (j >= gameInfo.boardWidth) return FALSE;
13589                 if(*p=='+') {
13590                     piece = CharToPiece(*++p);
13591                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13592                     piece = (ChessSquare) (PROMOTED piece ); p++;
13593                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13594                 } else piece = CharToPiece(*p++);
13595
13596                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13597                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13598                     piece = (ChessSquare) (PROMOTED piece);
13599                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13600                     p++;
13601                 }
13602                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13603             } else {
13604                 return FALSE;
13605             }
13606         }
13607     }
13608     while (*p == '/' || *p == ' ') p++;
13609
13610     /* [HGM] look for Crazyhouse holdings here */
13611     while(*p==' ') p++;
13612     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13613         if(*p == '[') p++;
13614         if(*p == '-' ) *p++; /* empty holdings */ else {
13615             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13616             /* if we would allow FEN reading to set board size, we would   */
13617             /* have to add holdings and shift the board read so far here   */
13618             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13619                 *p++;
13620                 if((int) piece >= (int) BlackPawn ) {
13621                     i = (int)piece - (int)BlackPawn;
13622                     i = PieceToNumber((ChessSquare)i);
13623                     if( i >= gameInfo.holdingsSize ) return FALSE;
13624                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13625                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13626                 } else {
13627                     i = (int)piece - (int)WhitePawn;
13628                     i = PieceToNumber((ChessSquare)i);
13629                     if( i >= gameInfo.holdingsSize ) return FALSE;
13630                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13631                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13632                 }
13633             }
13634         }
13635         if(*p == ']') *p++;
13636     }
13637
13638     while(*p == ' ') p++;
13639
13640     /* Active color */
13641     switch (*p++) {
13642       case 'w':
13643         *blackPlaysFirst = FALSE;
13644         break;
13645       case 'b': 
13646         *blackPlaysFirst = TRUE;
13647         break;
13648       default:
13649         return FALSE;
13650     }
13651
13652     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13653     /* return the extra info in global variiables             */
13654
13655     /* set defaults in case FEN is incomplete */
13656     FENepStatus = EP_UNKNOWN;
13657     for(i=0; i<nrCastlingRights; i++ ) {
13658         FENcastlingRights[i] =
13659             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13660     }   /* assume possible unless obviously impossible */
13661     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13662     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13663     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13664     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13665     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13666     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13667     FENrulePlies = 0;
13668
13669     while(*p==' ') p++;
13670     if(nrCastlingRights) {
13671       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13672           /* castling indicator present, so default becomes no castlings */
13673           for(i=0; i<nrCastlingRights; i++ ) {
13674                  FENcastlingRights[i] = -1;
13675           }
13676       }
13677       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13678              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13679              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13680              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13681         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13682
13683         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13684             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13685             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13686         }
13687         switch(c) {
13688           case'K':
13689               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13690               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13691               FENcastlingRights[2] = whiteKingFile;
13692               break;
13693           case'Q':
13694               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13695               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13696               FENcastlingRights[2] = whiteKingFile;
13697               break;
13698           case'k':
13699               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13700               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13701               FENcastlingRights[5] = blackKingFile;
13702               break;
13703           case'q':
13704               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13705               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13706               FENcastlingRights[5] = blackKingFile;
13707           case '-':
13708               break;
13709           default: /* FRC castlings */
13710               if(c >= 'a') { /* black rights */
13711                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13712                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13713                   if(i == BOARD_RGHT) break;
13714                   FENcastlingRights[5] = i;
13715                   c -= AAA;
13716                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13717                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13718                   if(c > i)
13719                       FENcastlingRights[3] = c;
13720                   else
13721                       FENcastlingRights[4] = c;
13722               } else { /* white rights */
13723                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13724                     if(board[0][i] == WhiteKing) break;
13725                   if(i == BOARD_RGHT) break;
13726                   FENcastlingRights[2] = i;
13727                   c -= AAA - 'a' + 'A';
13728                   if(board[0][c] >= WhiteKing) break;
13729                   if(c > i)
13730                       FENcastlingRights[0] = c;
13731                   else
13732                       FENcastlingRights[1] = c;
13733               }
13734         }
13735       }
13736     if (appData.debugMode) {
13737         fprintf(debugFP, "FEN castling rights:");
13738         for(i=0; i<nrCastlingRights; i++)
13739         fprintf(debugFP, " %d", FENcastlingRights[i]);
13740         fprintf(debugFP, "\n");
13741     }
13742
13743       while(*p==' ') p++;
13744     }
13745
13746     /* read e.p. field in games that know e.p. capture */
13747     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13748        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13749       if(*p=='-') {
13750         p++; FENepStatus = EP_NONE;
13751       } else {
13752          char c = *p++ - AAA;
13753
13754          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13755          if(*p >= '0' && *p <='9') *p++;
13756          FENepStatus = c;
13757       }
13758     }
13759
13760
13761     if(sscanf(p, "%d", &i) == 1) {
13762         FENrulePlies = i; /* 50-move ply counter */
13763         /* (The move number is still ignored)    */
13764     }
13765
13766     return TRUE;
13767 }
13768       
13769 void
13770 EditPositionPasteFEN(char *fen)
13771 {
13772   if (fen != NULL) {
13773     Board initial_position;
13774
13775     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13776       DisplayError(_("Bad FEN position in clipboard"), 0);
13777       return ;
13778     } else {
13779       int savedBlackPlaysFirst = blackPlaysFirst;
13780       EditPositionEvent();
13781       blackPlaysFirst = savedBlackPlaysFirst;
13782       CopyBoard(boards[0], initial_position);
13783           /* [HGM] copy FEN attributes as well */
13784           {   int i;
13785               initialRulePlies = FENrulePlies;
13786               epStatus[0] = FENepStatus;
13787               for( i=0; i<nrCastlingRights; i++ )
13788                   castlingRights[0][i] = FENcastlingRights[i];
13789           }
13790       EditPositionDone();
13791       DisplayBothClocks();
13792       DrawPosition(FALSE, boards[currentMove]);
13793     }
13794   }
13795 }